From 36f6c2a9bbd06f22d60719b60a717997eb44155e Mon Sep 17 00:00:00 2001 From: Vedant Gupta <115912707+im-vedant@users.noreply.github.com> Date: Sun, 29 Dec 2024 23:44:53 +0530 Subject: [PATCH] Merge develop-postgres into develop (#3077) * 20241229200436 Deleted all files in the develop branch in anticipation of merging develop-postgres into develop cleanly * 20241229200602 Merge develop-postgres into develop * commented out eslint disable and code coverage disable test * Fix errors * removed unnecessary code --------- Co-authored-by: im-vedant <194vedantgutpa@gmail.com> --- .eslintignore | 6 +- .eslintrc.json | 20 +- .github/workflows/auto-label.json5 | 14 +- .../workflows/code_coverage_disable_check.py | 167 + .github/workflows/countline.py | 0 .github/workflows/eslint_disable_check.py | 95 +- .github/workflows/pull-request.yml | 134 +- .github/workflows/push-deploy-website.yml | 57 + .github/workflows/push.yml | 4 +- .husky/post-merge | 0 .husky/pre-commit | 0 .idea/.gitignore | 5 - .idea/inspectionProfiles/Project_Default.xml | 6 - .idea/modules.xml | 8 - .idea/talawa-admin.iml | 12 - .idea/vcs.xml | 6 - .nojekyll | 0 config/docker/setup/nginx.conf | 48 - docker-compose.yml | 18 - docs/.gitignore | 22 + docs/CNAME | 1 + docs/README.md | 176 + docs/blog/2019-05-28-first-blog-post.md | 12 + docs/blog/2019-05-29-long-blog-post.md | 44 + docs/blog/2021-08-01-mdx-blog-post.mdx | 24 + .../docusaurus-plushie-banner.jpeg | Bin 0 -> 96122 bytes docs/blog/2021-08-26-welcome/index.md | 29 + docs/blog/authors.yml | 23 + docs/blog/tags.yml | 19 + docs/docs/intro.md | 47 + docs/docs/tutorial-basics/_category_.json | 8 + docs/docs/tutorial-basics/congratulations.md | 23 + .../tutorial-basics/create-a-blog-post.md | 34 + .../docs/tutorial-basics/create-a-document.md | 57 + docs/docs/tutorial-basics/create-a-page.md | 43 + docs/docs/tutorial-basics/deploy-your-site.md | 31 + .../tutorial-basics/markdown-features.mdx | 152 + docs/docs/tutorial-extras/_category_.json | 7 + .../img/docsVersionDropdown.png | Bin 0 -> 25427 bytes .../tutorial-extras/img/localeDropdown.png | Bin 0 -> 27841 bytes .../tutorial-extras/manage-docs-versions.md | 55 + .../tutorial-extras/translate-your-site.md | 88 + docs/docusaurus.config.ts | 119 + docs/package.json | 48 + docs/sidebars.ts | 33 + .../src/components/HomepageFeatures/index.tsx | 70 + .../HomepageFeatures/styles.module.css | 11 + docs/src/css/custom.css | 30 + docs/src/pages/index.module.css | 23 + docs/src/pages/index.tsx | 45 + docs/src/pages/markdown-page.md | 7 + docs/static/.nojekyll | 0 docs/static/CNAME | 1 + docs/static/img/docusaurus-social-card.jpg | Bin 0 -> 55746 bytes docs/static/img/docusaurus.png | Bin 0 -> 5142 bytes docs/static/img/favicon.ico | Bin 0 -> 3626 bytes docs/static/img/logo.svg | 1 + docs/static/img/markdown/misc/logo.png | Bin 0 -> 14141 bytes .../static/img/undraw_docusaurus_mountain.svg | 171 + docs/static/img/undraw_docusaurus_react.svg | 170 + docs/static/img/undraw_docusaurus_tree.svg | 40 + jest.config.js | 3 +- package-lock.json | 2573 +++++-- package.json | 10 +- public/images/svg/options-outline.svg | 11 + public/images/svg/organization.svg | 16 + public/locales/en/translation.json | 29 +- public/locales/fr/translation.json | 35 +- public/locales/hi/translation.json | 35 +- public/locales/sp/translation.json | 36 +- public/locales/zh/translation.json | 35 +- scripts/githooks/check-localstorage-usage.js | 4 +- src/{App.test.tsx => App.spec.tsx} | 37 +- src/App.tsx | 38 +- .../Mutations/ActionItemCategoryMutations.ts | 2 +- .../Mutations/OrganizationMutations.ts | 51 +- src/GraphQl/Mutations/mutations.ts | 2 + src/GraphQl/Queries/PlugInQueries.ts | 126 +- src/GraphQl/Queries/Queries.ts | 10 + src/assets/svgs/agenda-items.svg | 2 +- .../core/AddOnEntry/AddOnEntry.module.css | 24 - .../AddOn/core/AddOnEntry/AddOnEntry.test.tsx | 236 - .../AddOn/core/AddOnEntry/AddOnEntry.tsx | 2 +- ...gister.test.tsx => AddOnRegister.spec.tsx} | 29 +- ...ddOnStore.test.tsx => AddOnStore.spec.tsx} | 53 +- .../support/components/Action/Action.test.tsx | 24 - ...nContent.test.tsx => MainContent.spec.tsx} | 7 +- ...{SidePanel.test.tsx => SidePanel.spec.tsx} | 7 +- ...n.helper.test.ts => Plugin.helper.spec.ts} | 27 +- ...ments.test.tsx => Advertisements.spec.tsx} | 41 +- ...y.test.tsx => AdvertisementEntry.spec.tsx} | 90 +- ...est.tsx => AdvertisementRegister.spec.tsx} | 127 +- ...t.tsx => AgendaCategoryContainer.spec.tsx} | 9 +- .../AgendaCategoryContainerProps.ts | 5 +- ...test.tsx => AgendaItemsContainer.spec.tsx} | 12 +- .../AgendaItems/AgendaItemsContainerProps.ts | 5 +- ...st.tsx => AgendaItemsCreateModal.spec.tsx} | 18 +- ...t.tsx => AgendaItemsPreviewModal.spec.tsx} | 7 +- ...st.tsx => AgendaItemsUpdateModal.spec.tsx} | 19 +- src/components/Avatar/Avatar.spec.tsx | 1 - ...st.tsx => ChangeLanguageDropdown.spec.tsx} | 9 +- ...InModal.test.tsx => CheckInModal.spec.tsx} | 8 +- .../CheckIn/CheckInWrapper.module.css | 13 - ...apper.test.tsx => CheckInWrapper.spec.tsx} | 15 +- src/components/CheckIn/CheckInWrapper.tsx | 19 +- .../{TableRow.test.tsx => TableRow.spec.tsx} | 25 +- src/components/CheckIn/tagTemplate.ts | 22 +- ....test.tsx => CollapsibleDropdown.spec.tsx} | 34 +- .../ContriStats/ContriStats.module.css | 7 - ...triStats.test.tsx => ContriStats.spec.tsx} | 1 + src/components/ContriStats/ContriStats.tsx | 19 +- ...Down.test.tsx => DynamicDropDown.spec.tsx} | 15 +- ...t.tsx => EditCustomFieldDropDown.spec.tsx} | 10 +- .../EventCalendar/EventCalendar.module.css | 177 +- ...lendar.test.tsx => EventCalendar.spec.tsx} | 3 +- .../EventCalendar/EventCalendar.tsx | 240 +- ...ntHeader.test.tsx => EventHeader.spec.tsx} | 13 +- .../EventCalendar/YearlyEventCalender.tsx | 2 +- ...test.tsx => EventDashboardScreen.spec.tsx} | 35 +- ...stCard.test.tsx => EventListCard.spec.tsx} | 60 +- .../EventListCard/EventListCardModals.tsx | 9 +- .../Dashboard/EventDashboard.module.css | 101 - ...board.test.tsx => EventDashboard.spec.tsx} | 12 +- .../Dashboard/EventDashboard.tsx | 2 +- .../EventAgendaItems.module.css | 22 - ...ems.test.tsx => EventAgendaItems.spec.tsx} | 41 +- .../EventAgendaItems/EventAgendaItems.tsx | 2 +- ...st.test.tsx => AttendedEventList.spec.tsx} | 7 +- ...ance.test.tsx => EventAttendance.spec.tsx} | 22 +- .../EventAttendance/EventAttendance.tsx | 2 +- ...tics.test.tsx => EventStatistics.spec.tsx} | 39 +- .../EventAttendance/EventStatistics.tsx | 51 +- .../EventsAttendance.module.css | 35 - .../EventRegistrant/EventRegistrants.spec.tsx | 264 + .../EventRegistrant/EventRegistrants.tsx | 227 + .../EventRegistrant/Registrations.mocks.ts | 67 + ...ee.test.tsx => AddOnSpotAttendee.spec.tsx} | 24 +- ...est.tsx => EventRegistrantsModal.spec.tsx} | 3 +- .../EventRegistrantsWrapper.module.css | 13 - ...t.tsx => EventRegistrantsWrapper.spec.tsx} | 3 +- .../EventRegistrantsWrapper.tsx | 28 +- ...ventStats.test.tsx => EventStats.spec.tsx} | 16 +- ...er.test.tsx => EventStatsWrapper.spec.tsx} | 23 +- ...Rating.test.tsx => AverageRating.spec.tsx} | 3 +- .../{Feedback.test.tsx => Feedback.spec.tsx} | 16 +- .../{Review.test.tsx => Review.spec.tsx} | 5 +- .../GroupChatDetails.module.css | 94 + .../GroupChatDetails.test.tsx | 603 ++ .../GroupChatDetails/GroupChatDetails.tsx | 442 ++ ...ponent.test.tsx => IconComponent.spec.tsx} | 5 + .../IconComponent/IconComponent.tsx | 8 + ...test.tsx => InfiniteScrollLoader.spec.tsx} | 1 + .../LeftDrawer/LeftDrawer.module.css | 239 - src/components/LeftDrawer/LeftDrawer.tsx | 2 +- ...werOrg.test.tsx => LeftDrawerOrg.spec.tsx} | 15 +- .../{Loader.test.tsx => Loader.spec.tsx} | 7 +- .../LoginPortalToggle.module.css | 6 +- ...le.test.tsx => LoginPortalToggle.spec.tsx} | 3 +- ...st.tsx => EventsAttendedByMember.spec.tsx} | 0 ...st.tsx => EventsAttendedCardItem.spec.tsx} | 3 +- ...tsx => EventsAttendedMemberModal.spec.tsx} | 19 +- .../MemberDetail/customTableCell.spec.tsx | 160 + .../MemberDetail/customTableCell.test.tsx | 171 - ...rd.test.tsx => MemberRequestCard.spec.tsx} | 3 +- .../{NotFound.test.tsx => NotFound.spec.tsx} | 2 +- ...ard.test.tsx => OrgAdminListCard.spec.tsx} | 20 +- .../OrgContriCards/OrgContriCards.module.css | 22 - ...Cards.test.tsx => OrgContriCards.spec.tsx} | 2 +- .../OrgContriCards/OrgContriCards.tsx | 2 +- ...{OrgDelete.test.tsx => OrgDelete.spec.tsx} | 4 +- ...ListCard.test.tsx => OrgListCard.spec.tsx} | 55 +- ...rd.test.tsx => OrgPeopleListCard.spec.tsx} | 4 +- ...PostCard.test.tsx => OrgPostCard.spec.tsx} | 139 +- ...yModal.test.tsx => CategoryModal.spec.tsx} | 28 +- ...t.tsx => OrgActionItemCategories.spec.tsx} | 33 +- ...tsx => AgendaCategoryCreateModal.spec.tsx} | 28 +- ...tsx => AgendaCategoryUpdateModal.spec.tsx} | 30 +- ...sx => OrganizationAgendaCategory.spec.tsx} | 51 +- ...{DeleteOrg.test.tsx => DeleteOrg.spec.tsx} | 51 +- ...t.tsx => OrgProfileFieldSettings.spec.tsx} | 28 +- ...{OrgUpdate.test.tsx => OrgUpdate.spec.tsx} | 22 +- ...ard.test.tsx => OrganizationCard.spec.tsx} | 14 +- ...est.tsx => OrganizationCardStart.spec.tsx} | 5 +- .../OrganizationDashCards/CardItem.module.css | 81 - .../{CardItem.test.tsx => CardItem.spec.tsx} | 3 +- .../OrganizationDashCards/CardItem.tsx | 13 +- .../CardItemLoading.spec.tsx | 23 + .../OrganizationDashCards/CardItemLoading.tsx | 6 +- .../DashBoardCardLoading.spec.tsx | 28 + ...rdCard.test.tsx => DashboardCard.spec.tsx} | 3 +- .../OrganizationDashCards/DashboardCard.tsx | 2 +- .../DashboardCardLoading.tsx | 8 +- .../Dashboardcard.module.css | 60 - ...n.test.tsx => OrganizationScreen.spec.tsx} | 10 +- ...agination.test.tsx => Pagination.spec.tsx} | 5 +- ...down.test.tsx => ProfileDropdown.spec.tsx} | 139 +- ...nce.test.tsx => CustomRecurrence.spec.tsx} | 19 +- .../CustomRecurrenceModal.module.css | 59 - .../CustomRecurrenceModal.tsx | 6 +- ...ns.test.tsx => RecurrenceOptions.spec.tsx} | 19 +- ...em.test.tsx => RequestsTableItem.spec.tsx} | 85 +- .../RequestsTableItemMocks.ts | 8 +- ...een.test.tsx => SuperAdminScreen.spec.tsx} | 2 +- ...ession.test.tsx => UpdateSession.spec.tsx} | 67 +- ...istCard.test.tsx => UserListCard.spec.tsx} | 20 +- ...e.test.tsx => UserPasswordUpdate.spec.tsx} | 104 +- .../UserPasswordUpdateMocks.ts | 40 + .../UserPortal/ChatRoom/ChatRoom.module.css | 38 +- .../UserPortal/ChatRoom/ChatRoom.spec.tsx | 5985 +++++++++++++++++ .../UserPortal/ChatRoom/ChatRoom.test.tsx | 1586 ----- .../UserPortal/ChatRoom/ChatRoom.tsx | 184 +- ...mentCard.test.tsx => CommentCard.spec.tsx} | 27 +- .../ContactCard/ContactCard.module.css | 19 +- ...tactCard.test.tsx => ContactCard.spec.tsx} | 28 +- .../UserPortal/ContactCard/ContactCard.tsx | 13 +- .../CreateDirectChat.module.css | 9 - .../CreateDirectChat.spec.tsx | 4036 +++++++++++ .../CreateDirectChat.test.tsx | 1496 ---- .../CreateDirectChat/CreateDirectChat.tsx | 19 +- .../CreateGroupChat.module.css | 29 + .../CreateGroupChat/CreateGroupChat.spec.tsx | 5801 ++++++++++++++++ .../CreateGroupChat/CreateGroupChat.test.tsx | 2455 ------- .../CreateGroupChat/CreateGroupChat.tsx | 154 +- ...ionCard.test.tsx => DonationCard.spec.tsx} | 15 +- ...{EventCard.test.tsx => EventCard.spec.tsx} | 10 +- ...ard.test.tsx => OrganizationCard.spec.tsx} | 61 +- ...r.test.tsx => OrganizationNavbar.spec.tsx} | 68 +- ....test.tsx => OrganizationSidebar.spec.tsx} | 32 +- ...eopleCard.test.tsx => PeopleCard.spec.tsx} | 16 +- .../UserPortal/PostCard/PostCard.module.css | 183 - .../{PostCard.test.tsx => PostCard.spec.tsx} | 71 +- .../UserPortal/PostCard/PostCard.tsx | 18 +- ...tedPost.test.tsx => PromotedPost.spec.tsx} | 22 +- .../{Register.test.tsx => Register.spec.tsx} | 40 +- ....test.tsx => SecuredRouteForUser.spec.tsx} | 202 +- ...Modal.test.tsx => StartPostModal.spec.tsx} | 42 +- ...serNavbar.test.tsx => UserNavbar.spec.tsx} | 32 +- ...test.tsx => EventsAttendedByUser.spec.tsx} | 16 +- ...ds.test.tsx => UserAddressFields.spec.tsx} | 31 +- ...rSidebar.test.tsx => UserSidebar.spec.tsx} | 53 +- .../UserPortal/UserSidebar/UserSidebar.tsx | 23 - ...arOrg.test.tsx => UserSidebarOrg.spec.tsx} | 47 +- ...eleteUser.test.tsx => DeleteUser.spec.tsx} | 3 +- ...ttings.test.tsx => OtherSettings.spec.tsx} | 3 +- ...rProfile.test.tsx => UserProfile.spec.tsx} | 3 +- ...leItem.test.tsx => UserTableItem.spec.tsx} | 56 +- src/components/Venues/VenueCard.tsx | 2 +- ...enueModal.test.tsx => VenueModal.spec.tsx} | 35 +- ...{BlockUser.test.tsx => BlockUser.spec.tsx} | 301 +- .../CommunityProfile.module.css | 41 - .../CommunityProfile/CommunityProfile.tsx | 5 +- .../EventManagement/EventManagement.tsx | 5 +- .../EventVolunteers.module.css | 266 - .../EventVolunteers/Requests/Requests.tsx | 22 +- ...r.test.tsx => VolunteerContainer.spec.tsx} | 64 +- .../EventVolunteers/VolunteerContainer.tsx | 4 +- ...tsx => VolunteerGroupDeleteModal.spec.tsx} | 17 +- .../VolunteerGroupDeleteModal.tsx | 2 +- ....test.tsx => VolunteerGroupModal.spec.tsx} | 25 +- .../VolunteerGroups/VolunteerGroupModal.tsx | 2 +- ...t.tsx => VolunteerGroupViewModal.spec.tsx} | 5 +- .../VolunteerGroupViewModal.tsx | 10 +- ...oups.test.tsx => VolunteerGroups.spec.tsx} | 34 +- .../VolunteerGroups/VolunteerGroups.tsx | 26 +- ...test.tsx => VolunteerCreateModal.spec.tsx} | 17 +- .../Volunteers/VolunteerCreateModal.tsx | 2 +- ...test.tsx => VolunteerDeleteModal.spec.tsx} | 17 +- .../Volunteers/VolunteerDeleteModal.tsx | 2 +- ...l.test.tsx => VolunteerViewModal.spec.tsx} | 5 +- .../Volunteers/VolunteerViewModal.tsx | 6 +- ...olunteers.test.tsx => Volunteers.spec.tsx} | 64 +- .../EventVolunteers/Volunteers/Volunteers.tsx | 26 +- .../ForgotPassword/ForgotPassword.module.css | 71 - src/screens/ForgotPassword/ForgotPassword.tsx | 4 +- .../FundCampaignPledge.module.css | 273 - .../FundCampaignPledge/FundCampaignPledge.tsx | 28 +- .../FundCampaignPledge/PledgeDeleteModal.tsx | 2 +- .../FundCampaignPledge/PledgeModal.tsx | 12 +- ...derboard.test.tsx => Leaderboard.spec.tsx} | 47 +- src/screens/Leaderboard/Leaderboard.tsx | 4 +- src/screens/LoginPage/LoginPage.module.css | 269 - ...{LoginPage.test.tsx => LoginPage.spec.tsx} | 208 +- src/screens/LoginPage/LoginPage.tsx | 11 +- src/screens/ManageTag/ManageTag.module.css | 127 - ...{ManageTag.test.tsx => ManageTag.spec.tsx} | 61 +- src/screens/ManageTag/ManageTag.tsx | 43 +- ...rDetail.test.tsx => MemberDetail.spec.tsx} | 32 +- src/screens/MemberDetail/MemberDetail.tsx | 4 +- .../OrgContribution.module.css | 261 - ...tion.test.tsx => OrgContribution.spec.tsx} | 20 +- .../OrgContribution/OrgContribution.tsx | 7 +- src/screens/OrgList/OrgList.module.css | 324 - src/screens/OrgList/OrgList.test.tsx | 57 +- src/screens/OrgList/OrgList.tsx | 27 +- src/screens/OrgList/OrganizationModal.tsx | 4 +- src/screens/OrgPost/OrgPost.module.css | 325 - src/screens/OrgPost/OrgPost.tsx | 16 +- ...odal.test.tsx => ItemDeleteModal.spec.tsx} | 23 +- .../ItemDeleteModal.tsx | 2 +- ...{ItemModal.test.tsx => ItemModal.spec.tsx} | 33 +- ...est.tsx => ItemUpdateStatusModal.spec.tsx} | 19 +- ...wModal.test.tsx => ItemViewModal.spec.tsx} | 17 +- .../OrganizationActionItems/ItemViewModal.tsx | 4 +- .../OrganizationActionItem.mocks.ts | 10 + .../OrganizationActionItems.module.css | 291 - .../OrganizationActionItems.spec.tsx | 663 ++ .../OrganizationActionItems.test.tsx | 359 - .../OrganizationActionItems.tsx | 6 +- ...est.tsx => OrganizationDashboard.spec.tsx} | 66 +- .../OrganizationEvents.module.css | 389 -- .../OrganizationEvents/OrganizationEvents.tsx | 55 +- .../CampaignModal.tsx | 4 +- .../OrganizationFundCampagins.tsx | 16 +- .../OrganizationFundCampaign.module.css | 202 - ...{FundModal.test.tsx => FundModal.spec.tsx} | 47 +- src/screens/OrganizationFunds/FundModal.tsx | 8 +- .../OrganizationFunds.module.css | 142 - ...ds.test.tsx => OrganizationFunds.spec.tsx} | 41 +- .../OrganizationFunds/OrganizationFunds.tsx | 20 +- src/screens/OrganizationPeople/AddMember.tsx | 3 + ...e.test.tsx => OrganizationPeople.spec.tsx} | 62 +- .../OrganizationPeople/OrganizationPeople.tsx | 10 +- .../OrganizationVenues.module.css | 879 --- ...s.test.tsx => OrganizationVenues.spec.tsx} | 38 +- .../PageNotFound/PageNotFound.module.css | 109 - ...otFound.test.tsx => PageNotFound.spec.tsx} | 22 +- src/screens/PageNotFound/PageNotFound.tsx | 4 +- src/screens/SubTags/SubTags.module.css | 145 - .../{SubTags.test.tsx => SubTags.spec.tsx} | 39 +- src/screens/SubTags/SubTags.tsx | 32 +- src/screens/UserPortal/Chat/Chat.module.css | 32 +- src/screens/UserPortal/Chat/Chat.spec.tsx | 4414 ++++++++++++ src/screens/UserPortal/Chat/Chat.test.tsx | 1695 ----- src/screens/UserPortal/Chat/Chat.tsx | 272 +- .../UserPortal/Events/Events.module.css | 156 - .../{Events.test.tsx => Events.spec.tsx} | 330 +- src/screens/UserPortal/Events/Events.tsx | 50 +- .../LeaveOrganization.module.css | 27 + .../LeaveOrganization.test.tsx | 519 ++ .../LeaveOrganization/LeaveOrganization.tsx | 248 + ...ations.test.tsx => Organizations.spec.tsx} | 32 + .../{People.test.tsx => People.spec.tsx} | 44 +- src/screens/UserPortal/Posts/Posts.test.tsx | 5 +- .../{Settings.test.tsx => Settings.spec.tsx} | 71 +- src/screens/UserPortal/Settings/Settings.tsx | 15 + .../UserPortal/UserScreen/UserScreen.tsx | 1 + .../{Actions.test.tsx => Actions.spec.tsx} | 137 +- .../UserPortal/Volunteer/Actions/Actions.tsx | 2 +- .../Volunteer/Groups/GroupModal.tsx | 2 +- .../{Groups.test.tsx => Groups.spec.tsx} | 73 +- .../UserPortal/Volunteer/Groups/Groups.tsx | 2 +- ...itations.test.tsx => Invitations.spec.tsx} | 25 +- .../Volunteer/VolunteerManagement.test.tsx | 3 +- src/screens/Users/Users.spec.tsx | 1553 +++++ src/screens/Users/Users.test.tsx | 778 --- src/screens/Users/Users.tsx | 199 +- src/screens/Users/UsersMocks.ts | 1 - .../askForCustomPort/askForCustomPort.spec.ts | 106 + .../askForCustomPort/askForCustomPort.test.ts | 24 - .../askForCustomPort/askForCustomPort.ts | 65 +- .../askForTalawaApiUrl.test.ts | 14 +- .../askForTalawaApiUrl/askForTalawaApiUrl.ts | 2 +- ...est.ts => setupTalawaWebSocketUrl.spec.ts} | 23 +- ...ection.test.ts => checkConnection.spec.ts} | 12 +- ...ckEnvFile.test.ts => checkEnvFile.spec.ts} | 25 +- src/setupTests.ts | 3 +- .../{index.test.ts => index.spec.ts} | 9 +- .../{Action.test.ts => Action.spec.ts} | 7 +- ...nReducer.test.ts => pluginReducer.spec.ts} | 0 ...sReducer.test.ts => routesReducer.spec.ts} | 0 ...cer.test.ts => userRoutersReducer.spec.ts} | 16 + src/state/reducers/userRoutesReducer.ts | 6 + src/state/{store.test.tsx => store.spec.tsx} | 0 src/style/app.module.css | 3919 ++++++++++- src/utils/chartToPdf.spec.ts | 153 + src/utils/chartToPdf.test.ts | 168 - ...Base64.test.ts => convertToBase64.spec.ts} | 4 +- ...Handler.test.tsx => errorHandler.spec.tsx} | 7 +- src/utils/getRefreshToken.spec.ts | 87 + src/utils/getRefreshToken.test.ts | 54 - src/utils/getRefreshToken.ts | 2 +- ...are.test.ts => dateTimeMiddleware.spec.ts} | 31 +- src/utils/timezoneUtils/dateTimeMiddleware.ts | 20 +- ...torage.test.ts => useLocalstorage.spec.ts} | 11 +- src/utils/useSession.spec.tsx | 688 ++ src/utils/useSession.test.tsx | 544 -- vitest.config.ts | 1 + 387 files changed, 39112 insertions(+), 18460 deletions(-) create mode 100644 .github/workflows/code_coverage_disable_check.py mode change 100755 => 100644 .github/workflows/countline.py create mode 100644 .github/workflows/push-deploy-website.yml mode change 100755 => 100644 .husky/post-merge mode change 100755 => 100644 .husky/pre-commit delete mode 100644 .idea/.gitignore delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/talawa-admin.iml delete mode 100644 .idea/vcs.xml create mode 100644 .nojekyll delete mode 100644 config/docker/setup/nginx.conf delete mode 100644 docker-compose.yml create mode 100644 docs/.gitignore create mode 100644 docs/CNAME create mode 100644 docs/README.md create mode 100644 docs/blog/2019-05-28-first-blog-post.md create mode 100644 docs/blog/2019-05-29-long-blog-post.md create mode 100644 docs/blog/2021-08-01-mdx-blog-post.mdx create mode 100644 docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg create mode 100644 docs/blog/2021-08-26-welcome/index.md create mode 100644 docs/blog/authors.yml create mode 100644 docs/blog/tags.yml create mode 100644 docs/docs/intro.md create mode 100644 docs/docs/tutorial-basics/_category_.json create mode 100644 docs/docs/tutorial-basics/congratulations.md create mode 100644 docs/docs/tutorial-basics/create-a-blog-post.md create mode 100644 docs/docs/tutorial-basics/create-a-document.md create mode 100644 docs/docs/tutorial-basics/create-a-page.md create mode 100644 docs/docs/tutorial-basics/deploy-your-site.md create mode 100644 docs/docs/tutorial-basics/markdown-features.mdx create mode 100644 docs/docs/tutorial-extras/_category_.json create mode 100644 docs/docs/tutorial-extras/img/docsVersionDropdown.png create mode 100644 docs/docs/tutorial-extras/img/localeDropdown.png create mode 100644 docs/docs/tutorial-extras/manage-docs-versions.md create mode 100644 docs/docs/tutorial-extras/translate-your-site.md create mode 100644 docs/docusaurus.config.ts create mode 100644 docs/package.json create mode 100644 docs/sidebars.ts create mode 100644 docs/src/components/HomepageFeatures/index.tsx create mode 100644 docs/src/components/HomepageFeatures/styles.module.css create mode 100644 docs/src/css/custom.css create mode 100644 docs/src/pages/index.module.css create mode 100644 docs/src/pages/index.tsx create mode 100644 docs/src/pages/markdown-page.md create mode 100644 docs/static/.nojekyll create mode 100644 docs/static/CNAME create mode 100644 docs/static/img/docusaurus-social-card.jpg create mode 100644 docs/static/img/docusaurus.png create mode 100644 docs/static/img/favicon.ico create mode 100644 docs/static/img/logo.svg create mode 100644 docs/static/img/markdown/misc/logo.png create mode 100644 docs/static/img/undraw_docusaurus_mountain.svg create mode 100644 docs/static/img/undraw_docusaurus_react.svg create mode 100644 docs/static/img/undraw_docusaurus_tree.svg create mode 100644 public/images/svg/options-outline.svg create mode 100644 public/images/svg/organization.svg mode change 100755 => 100644 scripts/githooks/check-localstorage-usage.js rename src/{App.test.tsx => App.spec.tsx} (70%) delete mode 100644 src/components/AddOn/core/AddOnEntry/AddOnEntry.module.css delete mode 100644 src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx rename src/components/AddOn/core/AddOnRegister/{AddOnRegister.test.tsx => AddOnRegister.spec.tsx} (89%) rename src/components/AddOn/core/AddOnStore/{AddOnStore.test.tsx => AddOnStore.spec.tsx} (88%) delete mode 100644 src/components/AddOn/support/components/Action/Action.test.tsx rename src/components/AddOn/support/components/MainContent/{MainContent.test.tsx => MainContent.spec.tsx} (70%) rename src/components/AddOn/support/components/SidePanel/{SidePanel.test.tsx => SidePanel.spec.tsx} (82%) rename src/components/AddOn/support/services/{Plugin.helper.test.ts => Plugin.helper.spec.ts} (65%) rename src/components/Advertisements/{Advertisements.test.tsx => Advertisements.spec.tsx} (96%) rename src/components/Advertisements/core/AdvertisementEntry/{AdvertisementEntry.test.tsx => AdvertisementEntry.spec.tsx} (90%) rename src/components/Advertisements/core/AdvertisementRegister/{AdvertisementRegister.test.tsx => AdvertisementRegister.spec.tsx} (87%) rename src/components/AgendaCategory/{AgendaCategoryContainer.test.tsx => AgendaCategoryContainer.spec.tsx} (98%) rename src/components/AgendaItems/{AgendaItemsContainer.test.tsx => AgendaItemsContainer.spec.tsx} (98%) rename src/components/AgendaItems/{AgendaItemsCreateModal.test.tsx => AgendaItemsCreateModal.spec.tsx} (96%) rename src/components/AgendaItems/{AgendaItemsPreviewModal.test.tsx => AgendaItemsPreviewModal.spec.tsx} (94%) rename src/components/AgendaItems/{AgendaItemsUpdateModal.test.tsx => AgendaItemsUpdateModal.spec.tsx} (96%) rename src/components/ChangeLanguageDropdown/{ChangeLanguageDropdown.test.tsx => ChangeLanguageDropdown.spec.tsx} (93%) rename src/components/CheckIn/{CheckInModal.test.tsx => CheckInModal.spec.tsx} (89%) delete mode 100644 src/components/CheckIn/CheckInWrapper.module.css rename src/components/CheckIn/{CheckInWrapper.test.tsx => CheckInWrapper.spec.tsx} (76%) rename src/components/CheckIn/{TableRow.test.tsx => TableRow.spec.tsx} (91%) rename src/components/CollapsibleDropdown/{CollapsibleDropdown.test.tsx => CollapsibleDropdown.spec.tsx} (87%) delete mode 100644 src/components/ContriStats/ContriStats.module.css rename src/components/ContriStats/{ContriStats.test.tsx => ContriStats.spec.tsx} (96%) rename src/components/DynamicDropDown/{DynamicDropDown.test.tsx => DynamicDropDown.spec.tsx} (91%) rename src/components/EditCustomFieldDropDown/{EditCustomFieldDropDown.test.tsx => EditCustomFieldDropDown.spec.tsx} (90%) rename src/components/EventCalendar/{EventCalendar.test.tsx => EventCalendar.spec.tsx} (99%) rename src/components/EventCalendar/{EventHeader.test.tsx => EventHeader.spec.tsx} (92%) rename src/components/EventDashboardScreen/{EventDashboardScreen.test.tsx => EventDashboardScreen.spec.tsx} (85%) rename src/components/EventListCard/{EventListCard.test.tsx => EventListCard.spec.tsx} (92%) delete mode 100644 src/components/EventManagement/Dashboard/EventDashboard.module.css rename src/components/EventManagement/Dashboard/{EventDashboard.test.tsx => EventDashboard.spec.tsx} (88%) delete mode 100644 src/components/EventManagement/EventAgendaItems/EventAgendaItems.module.css rename src/components/EventManagement/EventAgendaItems/{EventAgendaItems.test.tsx => EventAgendaItems.spec.tsx} (87%) rename src/components/EventManagement/EventAttendance/{AttendedEventList.test.tsx => AttendedEventList.spec.tsx} (92%) rename src/components/EventManagement/EventAttendance/{EventAttendance.test.tsx => EventAttendance.spec.tsx} (85%) rename src/components/EventManagement/EventAttendance/{EventStatistics.test.tsx => EventStatistics.spec.tsx} (88%) delete mode 100644 src/components/EventManagement/EventAttendance/EventsAttendance.module.css create mode 100644 src/components/EventManagement/EventRegistrant/EventRegistrants.spec.tsx create mode 100644 src/components/EventManagement/EventRegistrant/EventRegistrants.tsx create mode 100644 src/components/EventManagement/EventRegistrant/Registrations.mocks.ts rename src/components/EventRegistrantsModal/{AddOnSpotAttendee.test.tsx => AddOnSpotAttendee.spec.tsx} (92%) rename src/components/EventRegistrantsModal/{EventRegistrantsModal.test.tsx => EventRegistrantsModal.spec.tsx} (99%) delete mode 100644 src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css rename src/components/EventRegistrantsModal/{EventRegistrantsWrapper.test.tsx => EventRegistrantsWrapper.spec.tsx} (95%) rename src/components/EventStats/{EventStats.test.tsx => EventStats.spec.tsx} (75%) rename src/components/EventStats/{EventStatsWrapper.test.tsx => EventStatsWrapper.spec.tsx} (73%) rename src/components/EventStats/Statistics/{AverageRating.test.tsx => AverageRating.spec.tsx} (91%) rename src/components/EventStats/Statistics/{Feedback.test.tsx => Feedback.spec.tsx} (81%) rename src/components/EventStats/Statistics/{Review.test.tsx => Review.spec.tsx} (89%) create mode 100644 src/components/GroupChatDetails/GroupChatDetails.module.css create mode 100644 src/components/GroupChatDetails/GroupChatDetails.test.tsx create mode 100644 src/components/GroupChatDetails/GroupChatDetails.tsx rename src/components/IconComponent/{IconComponent.test.tsx => IconComponent.spec.tsx} (94%) rename src/components/InfiniteScrollLoader/{InfiniteScrollLoader.test.tsx => InfiniteScrollLoader.spec.tsx} (90%) delete mode 100644 src/components/LeftDrawer/LeftDrawer.module.css rename src/components/LeftDrawerOrg/{LeftDrawerOrg.test.tsx => LeftDrawerOrg.spec.tsx} (98%) rename src/components/Loader/{Loader.test.tsx => Loader.spec.tsx} (76%) rename src/components/LoginPortalToggle/{LoginPortalToggle.test.tsx => LoginPortalToggle.spec.tsx} (92%) rename src/components/MemberDetail/{EventsAttendedByMember.test.tsx => EventsAttendedByMember.spec.tsx} (100%) rename src/components/MemberDetail/{EventsAttendedCardItem.test.tsx => EventsAttendedCardItem.spec.tsx} (96%) rename src/components/MemberDetail/{EventsAttendedMemberModal.test.tsx => EventsAttendedMemberModal.spec.tsx} (91%) create mode 100644 src/components/MemberDetail/customTableCell.spec.tsx delete mode 100644 src/components/MemberDetail/customTableCell.test.tsx rename src/components/MemberRequestCard/{MemberRequestCard.test.tsx => MemberRequestCard.spec.tsx} (97%) rename src/components/NotFound/{NotFound.test.tsx => NotFound.spec.tsx} (94%) rename src/components/OrgAdminListCard/{OrgAdminListCard.test.tsx => OrgAdminListCard.spec.tsx} (82%) delete mode 100644 src/components/OrgContriCards/OrgContriCards.module.css rename src/components/OrgContriCards/{OrgContriCards.test.tsx => OrgContriCards.spec.tsx} (97%) rename src/components/OrgDelete/{OrgDelete.test.tsx => OrgDelete.spec.tsx} (86%) rename src/components/OrgListCard/{OrgListCard.test.tsx => OrgListCard.spec.tsx} (73%) rename src/components/OrgPeopleListCard/{OrgPeopleListCard.test.tsx => OrgPeopleListCard.spec.tsx} (96%) rename src/components/OrgPostCard/{OrgPostCard.test.tsx => OrgPostCard.spec.tsx} (86%) rename src/components/OrgSettings/ActionItemCategories/{CategoryModal.test.tsx => CategoryModal.spec.tsx} (85%) rename src/components/OrgSettings/ActionItemCategories/{OrgActionItemCategories.test.tsx => OrgActionItemCategories.spec.tsx} (83%) rename src/components/OrgSettings/AgendaItemCategories/{AgendaCategoryCreateModal.test.tsx => AgendaCategoryCreateModal.spec.tsx} (76%) rename src/components/OrgSettings/AgendaItemCategories/{AgendaCategoryUpdateModal.test.tsx => AgendaCategoryUpdateModal.spec.tsx} (82%) rename src/components/OrgSettings/AgendaItemCategories/{OrganizationAgendaCategory.test.tsx => OrganizationAgendaCategory.spec.tsx} (82%) rename src/components/OrgSettings/General/DeleteOrg/{DeleteOrg.test.tsx => DeleteOrg.spec.tsx} (80%) rename src/components/OrgSettings/General/OrgProfileFieldSettings/{OrgProfileFieldSettings.test.tsx => OrgProfileFieldSettings.spec.tsx} (86%) rename src/components/OrgSettings/General/OrgUpdate/{OrgUpdate.test.tsx => OrgUpdate.spec.tsx} (90%) rename src/components/OrganizationCard/{OrganizationCard.test.tsx => OrganizationCard.spec.tsx} (66%) rename src/components/OrganizationCardStart/{OrganizationCardStart.test.tsx => OrganizationCardStart.spec.tsx} (76%) delete mode 100644 src/components/OrganizationDashCards/CardItem.module.css rename src/components/OrganizationDashCards/{CardItem.test.tsx => CardItem.spec.tsx} (98%) create mode 100644 src/components/OrganizationDashCards/CardItemLoading.spec.tsx create mode 100644 src/components/OrganizationDashCards/DashBoardCardLoading.spec.tsx rename src/components/OrganizationDashCards/{DashboardCard.test.tsx => DashboardCard.spec.tsx} (99%) delete mode 100644 src/components/OrganizationDashCards/Dashboardcard.module.css rename src/components/OrganizationScreen/{OrganizationScreen.test.tsx => OrganizationScreen.spec.tsx} (94%) rename src/components/Pagination/{Pagination.test.tsx => Pagination.spec.tsx} (89%) rename src/components/ProfileDropdown/{ProfileDropdown.test.tsx => ProfileDropdown.spec.tsx} (52%) rename src/components/RecurrenceOptions/{CustomRecurrence.test.tsx => CustomRecurrence.spec.tsx} (98%) delete mode 100644 src/components/RecurrenceOptions/CustomRecurrenceModal.module.css rename src/components/RecurrenceOptions/{RecurrenceOptions.test.tsx => RecurrenceOptions.spec.tsx} (97%) rename src/components/RequestsTableItem/{RequestsTableItem.test.tsx => RequestsTableItem.spec.tsx} (62%) rename src/components/SuperAdminScreen/{SuperAdminScreen.test.tsx => SuperAdminScreen.spec.tsx} (97%) rename src/components/UpdateSession/{UpdateSession.test.tsx => UpdateSession.spec.tsx} (81%) rename src/components/UserListCard/{UserListCard.test.tsx => UserListCard.spec.tsx} (77%) rename src/components/UserPasswordUpdate/{UserPasswordUpdate.test.tsx => UserPasswordUpdate.spec.tsx} (58%) create mode 100644 src/components/UserPasswordUpdate/UserPasswordUpdateMocks.ts create mode 100644 src/components/UserPortal/ChatRoom/ChatRoom.spec.tsx delete mode 100644 src/components/UserPortal/ChatRoom/ChatRoom.test.tsx rename src/components/UserPortal/CommentCard/{CommentCard.test.tsx => CommentCard.spec.tsx} (85%) rename src/components/UserPortal/ContactCard/{ContactCard.test.tsx => ContactCard.spec.tsx} (72%) delete mode 100644 src/components/UserPortal/CreateDirectChat/CreateDirectChat.module.css create mode 100644 src/components/UserPortal/CreateDirectChat/CreateDirectChat.spec.tsx delete mode 100644 src/components/UserPortal/CreateDirectChat/CreateDirectChat.test.tsx create mode 100644 src/components/UserPortal/CreateGroupChat/CreateGroupChat.spec.tsx delete mode 100644 src/components/UserPortal/CreateGroupChat/CreateGroupChat.test.tsx rename src/components/UserPortal/DonationCard/{DonationCard.test.tsx => DonationCard.spec.tsx} (64%) rename src/components/UserPortal/EventCard/{EventCard.test.tsx => EventCard.spec.tsx} (94%) rename src/components/UserPortal/OrganizationCard/{OrganizationCard.test.tsx => OrganizationCard.spec.tsx} (77%) rename src/components/UserPortal/OrganizationNavbar/{OrganizationNavbar.test.tsx => OrganizationNavbar.spec.tsx} (81%) rename src/components/UserPortal/OrganizationSidebar/{OrganizationSidebar.test.tsx => OrganizationSidebar.spec.tsx} (74%) rename src/components/UserPortal/PeopleCard/{PeopleCard.test.tsx => PeopleCard.spec.tsx} (65%) delete mode 100644 src/components/UserPortal/PostCard/PostCard.module.css rename src/components/UserPortal/PostCard/{PostCard.test.tsx => PostCard.spec.tsx} (90%) rename src/components/UserPortal/PromotedPost/{PromotedPost.test.tsx => PromotedPost.spec.tsx} (77%) rename src/components/UserPortal/Register/{Register.test.tsx => Register.spec.tsx} (80%) rename src/components/UserPortal/SecuredRouteForUser/{SecuredRouteForUser.test.tsx => SecuredRouteForUser.spec.tsx} (79%) rename src/components/UserPortal/StartPostModal/{StartPostModal.test.tsx => StartPostModal.spec.tsx} (71%) rename src/components/UserPortal/UserNavbar/{UserNavbar.test.tsx => UserNavbar.spec.tsx} (74%) rename src/components/UserPortal/UserProfile/{EventsAttendedByUser.test.tsx => EventsAttendedByUser.spec.tsx} (76%) rename src/components/UserPortal/UserProfile/{UserAddressFields.test.tsx => UserAddressFields.spec.tsx} (69%) rename src/components/UserPortal/UserSidebar/{UserSidebar.test.tsx => UserSidebar.spec.tsx} (85%) rename src/components/UserPortal/UserSidebarOrg/{UserSidebarOrg.test.tsx => UserSidebarOrg.spec.tsx} (86%) rename src/components/UserProfileSettings/{DeleteUser.test.tsx => DeleteUser.spec.tsx} (90%) rename src/components/UserProfileSettings/{OtherSettings.test.tsx => OtherSettings.spec.tsx} (89%) rename src/components/UserProfileSettings/{UserProfile.test.tsx => UserProfile.spec.tsx} (92%) rename src/components/UsersTableItem/{UserTableItem.test.tsx => UserTableItem.spec.tsx} (98%) rename src/components/Venues/{VenueModal.test.tsx => VenueModal.spec.tsx} (93%) rename src/screens/BlockUser/{BlockUser.test.tsx => BlockUser.spec.tsx} (52%) delete mode 100644 src/screens/CommunityProfile/CommunityProfile.module.css delete mode 100644 src/screens/EventVolunteers/EventVolunteers.module.css rename src/screens/EventVolunteers/{VolunteerContainer.test.tsx => VolunteerContainer.spec.tsx} (71%) rename src/screens/EventVolunteers/VolunteerGroups/{VolunteerGroupDeleteModal.test.tsx => VolunteerGroupDeleteModal.spec.tsx} (92%) rename src/screens/EventVolunteers/VolunteerGroups/{VolunteerGroupModal.test.tsx => VolunteerGroupModal.spec.tsx} (96%) rename src/screens/EventVolunteers/VolunteerGroups/{VolunteerGroupViewModal.test.tsx => VolunteerGroupViewModal.spec.tsx} (98%) rename src/screens/EventVolunteers/VolunteerGroups/{VolunteerGroups.test.tsx => VolunteerGroups.spec.tsx} (89%) rename src/screens/EventVolunteers/Volunteers/{VolunteerCreateModal.test.tsx => VolunteerCreateModal.spec.tsx} (91%) rename src/screens/EventVolunteers/Volunteers/{VolunteerDeleteModal.test.tsx => VolunteerDeleteModal.spec.tsx} (91%) rename src/screens/EventVolunteers/Volunteers/{VolunteerViewModal.test.tsx => VolunteerViewModal.spec.tsx} (97%) rename src/screens/EventVolunteers/Volunteers/{Volunteers.test.tsx => Volunteers.spec.tsx} (83%) delete mode 100644 src/screens/ForgotPassword/ForgotPassword.module.css delete mode 100644 src/screens/FundCampaignPledge/FundCampaignPledge.module.css rename src/screens/Leaderboard/{Leaderboard.test.tsx => Leaderboard.spec.tsx} (81%) delete mode 100644 src/screens/LoginPage/LoginPage.module.css rename src/screens/LoginPage/{LoginPage.test.tsx => LoginPage.spec.tsx} (80%) delete mode 100644 src/screens/ManageTag/ManageTag.module.css rename src/screens/ManageTag/{ManageTag.test.tsx => ManageTag.spec.tsx} (87%) rename src/screens/MemberDetail/{MemberDetail.test.tsx => MemberDetail.spec.tsx} (95%) delete mode 100644 src/screens/OrgContribution/OrgContribution.module.css rename src/screens/OrgContribution/{OrgContribution.test.tsx => OrgContribution.spec.tsx} (80%) delete mode 100644 src/screens/OrgList/OrgList.module.css delete mode 100644 src/screens/OrgPost/OrgPost.module.css rename src/screens/OrganizationActionItems/{ItemDeleteModal.test.tsx => ItemDeleteModal.spec.tsx} (91%) rename src/screens/OrganizationActionItems/{ItemModal.test.tsx => ItemModal.spec.tsx} (98%) rename src/screens/OrganizationActionItems/{ItemUpdateStatusModal.test.tsx => ItemUpdateStatusModal.spec.tsx} (96%) rename src/screens/OrganizationActionItems/{ItemViewModal.test.tsx => ItemViewModal.spec.tsx} (97%) delete mode 100644 src/screens/OrganizationActionItems/OrganizationActionItems.module.css create mode 100644 src/screens/OrganizationActionItems/OrganizationActionItems.spec.tsx delete mode 100644 src/screens/OrganizationActionItems/OrganizationActionItems.test.tsx rename src/screens/OrganizationDashboard/{OrganizationDashboard.test.tsx => OrganizationDashboard.spec.tsx} (78%) delete mode 100644 src/screens/OrganizationEvents/OrganizationEvents.module.css delete mode 100644 src/screens/OrganizationFundCampaign/OrganizationFundCampaign.module.css rename src/screens/OrganizationFunds/{FundModal.test.tsx => FundModal.spec.tsx} (85%) delete mode 100644 src/screens/OrganizationFunds/OrganizationFunds.module.css rename src/screens/OrganizationFunds/{OrganizationFunds.test.tsx => OrganizationFunds.spec.tsx} (85%) rename src/screens/OrganizationPeople/{OrganizationPeople.test.tsx => OrganizationPeople.spec.tsx} (95%) delete mode 100644 src/screens/OrganizationVenues/OrganizationVenues.module.css rename src/screens/OrganizationVenues/{OrganizationVenues.test.tsx => OrganizationVenues.spec.tsx} (92%) delete mode 100644 src/screens/PageNotFound/PageNotFound.module.css rename src/screens/PageNotFound/{PageNotFound.test.tsx => PageNotFound.spec.tsx} (68%) delete mode 100644 src/screens/SubTags/SubTags.module.css rename src/screens/SubTags/{SubTags.test.tsx => SubTags.spec.tsx} (87%) create mode 100644 src/screens/UserPortal/Chat/Chat.spec.tsx delete mode 100644 src/screens/UserPortal/Chat/Chat.test.tsx delete mode 100644 src/screens/UserPortal/Events/Events.module.css rename src/screens/UserPortal/Events/{Events.test.tsx => Events.spec.tsx} (62%) create mode 100644 src/screens/UserPortal/LeaveOrganization/LeaveOrganization.module.css create mode 100644 src/screens/UserPortal/LeaveOrganization/LeaveOrganization.test.tsx create mode 100644 src/screens/UserPortal/LeaveOrganization/LeaveOrganization.tsx rename src/screens/UserPortal/Organizations/{Organizations.test.tsx => Organizations.spec.tsx} (95%) rename src/screens/UserPortal/People/{People.test.tsx => People.spec.tsx} (82%) rename src/screens/UserPortal/Settings/{Settings.test.tsx => Settings.spec.tsx} (85%) rename src/screens/UserPortal/Volunteer/Actions/{Actions.test.tsx => Actions.spec.tsx} (56%) rename src/screens/UserPortal/Volunteer/Groups/{Groups.test.tsx => Groups.spec.tsx} (76%) rename src/screens/UserPortal/Volunteer/Invitations/{Invitations.test.tsx => Invitations.spec.tsx} (96%) create mode 100644 src/screens/Users/Users.spec.tsx delete mode 100644 src/screens/Users/Users.test.tsx create mode 100644 src/setup/askForCustomPort/askForCustomPort.spec.ts delete mode 100644 src/setup/askForCustomPort/askForCustomPort.test.ts rename src/setup/askForTalawaApiUrl/{setupTalawaWebSocketUrl.test.ts => setupTalawaWebSocketUrl.spec.ts} (69%) rename src/setup/checkConnection/{checkConnection.test.ts => checkConnection.spec.ts} (84%) rename src/setup/checkEnvFile/{checkEnvFile.test.ts => checkEnvFile.spec.ts} (62%) rename src/state/action-creators/{index.test.ts => index.spec.ts} (90%) rename src/state/helpers/{Action.test.ts => Action.spec.ts} (53%) rename src/state/reducers/{pluginReducer.test.ts => pluginReducer.spec.ts} (100%) rename src/state/reducers/{routesReducer.test.ts => routesReducer.spec.ts} (100%) rename src/state/reducers/{userRoutersReducer.test.ts => userRoutersReducer.spec.ts} (82%) rename src/state/{store.test.tsx => store.spec.tsx} (100%) create mode 100644 src/utils/chartToPdf.spec.ts delete mode 100644 src/utils/chartToPdf.test.ts rename src/utils/{convertToBase64.test.ts => convertToBase64.spec.ts} (90%) rename src/utils/{errorHandler.test.tsx => errorHandler.spec.tsx} (96%) create mode 100644 src/utils/getRefreshToken.spec.ts delete mode 100644 src/utils/getRefreshToken.test.ts rename src/utils/timezoneUtils/{dateTimeMiddleware.test.ts => dateTimeMiddleware.spec.ts} (92%) rename src/utils/{useLocalstorage.test.ts => useLocalstorage.spec.ts} (91%) create mode 100644 src/utils/useSession.spec.tsx delete mode 100644 src/utils/useSession.test.tsx diff --git a/.eslintignore b/.eslintignore index 23e76592c1..ee98c5bdb0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,6 @@ src/components/CheckIn/tagTemplate.ts package.json package-lock.json tsconfig.json -docker-compose.yml -Dockerfile -nginx.conf \ No newline at end of file + +# Ignore the Docusaurus website subdirectory +docs/** \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 165e406024..9ac9fa1c39 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -143,5 +143,23 @@ "version": "detect" } }, - "ignorePatterns": ["**/*.css", "**/*.scss", "**/*.less", "**/*.json"] + "ignorePatterns": [ + "**/*.css", + "**/*.scss", + "**/*.less", + "**/*.json", + "**/*.svg", + "docs/docusaurus.config.ts", + "docs/sidebars.ts", + "docs/src/**", + "docs/blog/**" + ], + "overrides": [ + { + "files": ["*.js", "*.jsx"], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off" + } + } + ] } diff --git a/.github/workflows/auto-label.json5 b/.github/workflows/auto-label.json5 index 37929ea97b..86bbddf033 100644 --- a/.github/workflows/auto-label.json5 +++ b/.github/workflows/auto-label.json5 @@ -1,8 +1,8 @@ -{ - "labelsSynonyms": { - "dependencies": ["dependabot", "dependency", "dependencies"], - "security": ["security"], - "ui/ux": ["layout", "screen", "design", "figma"] - }, - "defaultLabels": ["unapproved"], +{ + "labelsSynonyms": { + "dependencies": ["dependabot", "dependency", "dependencies"], + "security": ["security"], + "ui/ux": ["layout", "screen", "design", "figma"] + }, + "defaultLabels": ["unapproved"], } \ No newline at end of file diff --git a/.github/workflows/code_coverage_disable_check.py b/.github/workflows/code_coverage_disable_check.py new file mode 100644 index 0000000000..1a55c3f720 --- /dev/null +++ b/.github/workflows/code_coverage_disable_check.py @@ -0,0 +1,167 @@ +"""Code Coverage Disable Checker Script. + +Methodology: + + Recursively analyzes TypeScript files in the specified directories or + checks specific files + to ensure they do not contain code coverage disable statements. + + This script enforces proper code coverage practices in the project. + +NOTE: + This script complies with our python3 coding and documentation standards. + It complies with: + + 1) Pylint + 2) Pydocstyle + 3) Pycodestyle + 4) Flake8 + 5) Python Black + +""" + +import os +import re +import argparse +import sys + + +def has_code_coverage_disable(file_path): + """ + Check if a TypeScript file contains code coverage disable statements. + + Args: + file_path (str): Path to the TypeScript file. + + Returns: + bool: True if code coverage disable statement is found, False + otherwise. + """ + code_coverage_disable_pattern = re.compile( + r"""//?\s*istanbul\s+ignore(?:\s+(?:next|-line))?[^\n]*| + /\*\s*istanbul\s+ignore\s+(?:next|-line)\s*\*/""", + re.IGNORECASE, + ) + try: + with open(file_path, "r", encoding="utf-8") as file: + content = file.read() + return bool(code_coverage_disable_pattern.search(content)) + except FileNotFoundError: + print(f"File not found: {file_path}") + return False + except PermissionError: + print(f"Permission denied: {file_path}") + return False + except (IOError, OSError) as e: + print(f"Error reading file {file_path}: {e}") + return False + + +def check_code_coverage(files_or_dirs): + """ + Check TypeScript files for code coverage disable statements. + + Args: + files_or_dirs (list): List of files or directories to check. + + Returns: + bool: True if code coverage disable statement is found, False + otherwise. + """ + code_coverage_found = False + + for item in files_or_dirs: + if os.path.isdir(item): + # If it's a directory, recursively walk through the files in it + for root, _, files in os.walk(item): + if "node_modules" in root: + continue + for file_name in files: + if ( + file_name.endswith(".tsx") + or file_name.endswith(".ts") + and not file_name.endswith(".test.tsx") + and not file_name.endswith(".test.ts") + and not file_name.endswith(".spec.tsx") + and not file_name.endswith(".spec.ts") + ): + file_path = os.path.join(root, file_name) + if has_code_coverage_disable(file_path): + print( + f"""File {file_path} contains code coverage disable statement.""" + ) + code_coverage_found = True + elif os.path.isfile(item): + # If it's a file, check it directly + if ( + item.endswith(".tsx") + or item.endswith(".ts") + and not item.endswith(".test.tsx") + and not item.endswith(".test.ts") + and not item.endswith(".spec.tsx") + and not item.endswith(".spec.ts") + ): + if has_code_coverage_disable(item): + print( + f"""File {item} contains code coverage disable statement. Please remove it and add the appropriate tests.""" + ) + code_coverage_found = True + + return code_coverage_found + + +def arg_parser_resolver(): + """Resolve the CLI arguments provided by the user. + + Returns: + result: Parsed argument object + """ + parser = argparse.ArgumentParser() + parser.add_argument( + "--directory", + type=str, + nargs="+", + default=[os.getcwd()], + help="""One or more directories to check for code coverage disable + statements (default: current directory).""", + ) + parser.add_argument( + "--files", + type=str, + nargs="+", + default=[], + help="""One or more files to check directly for code coverage disable + statements (default: check directories).""", + ) + return parser.parse_args() + + +def main(): + """ + Execute the script's main functionality. + + This function serves as the entry point for the script. It performs + the following tasks: + 1. Validates and retrieves the files or directories to check from + command line arguments. + 2. Checks files or directories for code coverage disable statements. + 3. Provides informative messages based on the analysis. + 4. Exits with an error if code coverage disable statements are found. + + Raises: + SystemExit: If an error occurs during execution. + """ + args = arg_parser_resolver() + files_or_dirs = args.files if args.files else args.directory + # Check code coverage in the specified files or directories + code_coverage_found = check_code_coverage(files_or_dirs) + + if code_coverage_found: + print("Code coverage disable check failed. Exiting with error.") + sys.exit(1) + + print("Code coverage disable check completed successfully.") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/countline.py b/.github/workflows/countline.py old mode 100755 new mode 100644 diff --git a/.github/workflows/eslint_disable_check.py b/.github/workflows/eslint_disable_check.py index 201b4462b8..a24a80949e 100644 --- a/.github/workflows/eslint_disable_check.py +++ b/.github/workflows/eslint_disable_check.py @@ -4,13 +4,13 @@ Methodology: - Recursively analyzes TypeScript files in the 'src' directory and its subdirectories - as well as 'setup.ts' files to ensure they do not contain eslint-disable statements. + Recursively analyzes TypeScript files in the specified directory + or checks specific files directly to ensure they do not contain + eslint-disable statements. This script enforces code quality practices in the project. NOTE: - This script complies with our python3 coding and documentation standards. It complies with: @@ -18,14 +18,15 @@ 2) Pydocstyle 3) Pycodestyle 4) Flake8 + 5) Python Black """ - import os import re import argparse import sys + def has_eslint_disable(file_path): """ Check if a TypeScript file contains eslint-disable statements. @@ -36,43 +37,65 @@ def has_eslint_disable(file_path): Returns: bool: True if eslint-disable statement is found, False otherwise. """ - eslint_disable_pattern = re.compile(r'//\s*eslint-disable(?:-next-line|-line)?', re.IGNORECASE) - + eslint_disable_pattern = re.compile( + r"""\/\/\s*eslint-disable(?:-next-line + |-line)?[^\n]*|\/\*\s*eslint-disable[^\*]*\*\/""", + re.IGNORECASE, + ) + try: - with open(file_path, 'r', encoding='utf-8') as file: + with open(file_path, "r", encoding="utf-8") as file: content = file.read() return bool(eslint_disable_pattern.search(content)) - except Exception as e: + except FileNotFoundError: + print(f"File not found: {file_path}") + return False + except PermissionError: + print(f"Permission denied: {file_path}") + return False + except (IOError, OSError) as e: print(f"Error reading file {file_path}: {e}") return False -def check_eslint(directory): + +def check_eslint(files_or_directories): """ - Recursively check TypeScript files for eslint-disable statements in the 'src' directory. + Check TypeScript files for eslint-disable statements. Args: - directory (str): Path to the directory. + files_or_directories (list): List of files or directories to check. Returns: bool: True if eslint-disable statement is found, False otherwise. """ eslint_found = False - for root, dirs, files in os.walk(os.path.join(directory, 'src')): - for file_name in files: - if file_name.endswith('.tsx') and not file_name.endswith('.test.tsx'): - file_path = os.path.join(root, file_name) - if has_eslint_disable(file_path): - print(f'File {file_path} contains eslint-disable statement.') + for item in files_or_directories: + if os.path.isfile(item): + # If it's a file, directly check it + if item.endswith(".ts") or item.endswith(".tsx"): + if has_eslint_disable(item): + print(f"File {item} contains eslint-disable statement. Please remove them and ensure the code adheres to the specified ESLint rules.") eslint_found = True - - setup_path = os.path.join(directory, 'setup.ts') - if os.path.exists(setup_path) and has_eslint_disable(setup_path): - print(f'Setup file {setup_path} contains eslint-disable statement.') - eslint_found = True + elif os.path.isdir(item): + # If it's a directory, walk through it and check all + # .ts and .tsx files + for root, _, files in os.walk(item): + if "node_modules" in root: + continue + for file_name in files: + if file_name.endswith(".ts") or file_name.endswith(".tsx"): + file_path = os.path.join(root, file_name) + if has_eslint_disable(file_path): + print( + f"""File {file_path} contains eslint-disable + statement.""" + ) + eslint_found = True return eslint_found + def arg_parser_resolver(): """Resolve the CLI arguments provided by the user. @@ -80,21 +103,32 @@ def arg_parser_resolver(): result: Parsed argument object """ parser = argparse.ArgumentParser() + parser.add_argument( + "--files", + type=str, + nargs="+", + default=[], + help="""List of files to check for eslint disable + statements (default: None).""", + ) parser.add_argument( "--directory", type=str, - default=os.getcwd(), - help="Path to the directory to check (default: current directory)" + nargs="+", + default=[os.getcwd()], + help="""One or more directories to check for eslint disable + statements (default: current directory).""", ) return parser.parse_args() + def main(): """ Execute the script's main functionality. This function serves as the entry point for the script. It performs the following tasks: - 1. Validates and retrieves the directory to check from + 1. Validates and retrieves the files and directories to check from command line arguments. 2. Recursively checks TypeScript files for eslint-disable statements. 3. Provides informative messages based on the analysis. @@ -105,12 +139,10 @@ def main(): """ args = arg_parser_resolver() - if not os.path.exists(args.directory): - print(f"Error: The specified directory '{args.directory}' does not exist.") - sys.exit(1) - - # Check eslint in the specified directory - eslint_found = check_eslint(args.directory) + # Determine whether to check files or directories based on the arguments + files_or_directories = args.files if args.files else args.directory + # Check eslint in the specified files or directories + eslint_found = check_eslint(files_or_directories) if eslint_found: print("ESLint-disable check failed. Exiting with error.") @@ -118,5 +150,6 @@ def main(): print("ESLint-disable check completed successfully.") + if __name__ == "__main__": main() diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f5b780ff9e..edc6a58c7c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -59,7 +59,7 @@ jobs: if: steps.changed-files.outputs.only_changed != 'true' env: CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} - run: npx eslint ${CHANGED_FILES} && python .github/workflows/eslint_disable_check.py + run: npx eslint ${CHANGED_FILES} - name: Check for TSDoc comments run: npm run check-tsdoc # Run the TSDoc check script @@ -104,12 +104,14 @@ jobs: src/style/** schema.graphql package.json + package-lock.json tsconfig.json .gitignore .eslintrc.json .eslintignore .prettierrc .prettierignore + .nojekyll vite.config.ts docker-compose.yaml Dockerfile @@ -140,6 +142,7 @@ jobs: for file in ${CHANGED_UNAUTH_FILES}; do echo "$file is unauthorized to change/delete" done + echo "To override this, apply the 'ignore-sensitive-files-pr' label" exit 1 Count-Changed-Files: @@ -171,26 +174,51 @@ jobs: echo "- Source branch may be incorrect please use develop as source branch." exit 1 - Check-ESlint-Disable: - name: Check for eslint-disable - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.9 - - - name: Run Python script - run: | - python .github/workflows/eslint_disable_check.py + # Check-ESlint-Disable: + # name: Check for eslint-disable + # runs-on: ubuntu-latest + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Get changed files + # id: changed-files + # uses: tj-actions/changed-files@v45 + + # - name: Set up Python + # uses: actions/setup-python@v5 + # with: + # python-version: 3.9 + + # - name: Run Python script + # run: | + # python .github/workflows/eslint_disable_check.py --files ${{ steps.changed-files.outputs.all_changed_files }} + + # Check-Code-Coverage-Disable: + # name: Check for code coverage disable + # runs-on: ubuntu-latest + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Get changed files + # id: changed-files + # uses: tj-actions/changed-files@v45 + + # - name: Set up Python + # uses: actions/setup-python@v5 + # with: + # python-version: 3.9 + + # - name: Run Python script + # run: | + # python .github/workflows/code_coverage_disable_check.py --files ${{ steps.changed-files.outputs.all_changed_files }} Test-Application: name: Test Application runs-on: ubuntu-latest - needs: [Code-Quality-Checks, Check-ESlint-Disable] + needs: [Code-Quality-Checks] + # needs: [Code-Quality-Checks, Check-ESlint-Disable,Check-Code-Coverage-Disable] steps: - name: Checkout the Repository uses: actions/checkout@v4 @@ -243,6 +271,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true + gcov_ignore: 'docs/' fail_ci_if_error: false files: './coverage/lcov.info' name: '${{env.CODECOV_UNIQUE_NAME}}' @@ -253,33 +282,34 @@ jobs: path: "./coverage/lcov.info" min_coverage: 0.0 - Graphql-Inspector: - if: ${{ github.actor != 'dependabot[bot]' }} - name: Runs Introspection on the GitHub talawa-api repo on the schema.graphql file - runs-on: ubuntu-latest - steps: - - name: Checkout the Repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '22.x' - - - name: resolve dependency - run: npm install -g @graphql-inspector/cli + # Graphql-Inspector: + # if: ${{ github.actor != 'dependabot[bot]' }} + # name: Runs Introspection on the GitHub talawa-api repo on the schema.graphql file + # runs-on: ubuntu-latest + # steps: + # - name: Checkout the Repository + # uses: actions/checkout@v4 + + # - name: Set up Node.js + # uses: actions/setup-node@v4 + # with: + # node-version: '22.x' + + # - name: resolve dependency + # run: npm install -g @graphql-inspector/cli - - name: Clone API Repository - run: | - # Retrieve the complete branch name directly from the GitHub context - FULL_BRANCH_NAME=${{ github.base_ref }} - echo "FULL_Branch_NAME: $FULL_BRANCH_NAME" + # - name: Clone API Repository + # run: | + # # Retrieve the complete branch name directly from the GitHub context + # FULL_BRANCH_NAME=${{ github.base_ref }} + # echo "FULL_Branch_NAME: $FULL_BRANCH_NAME" - # Clone the specified repository using the extracted branch name - git clone --branch $FULL_BRANCH_NAME https://github.com/PalisadoesFoundation/talawa-api && ls -a + # # Clone the specified repository using the extracted branch name + # git clone --branch $FULL_BRANCH_NAME https://github.com/PalisadoesFoundation/talawa-api && ls -a - - name: Validate Documents - run: graphql-inspector validate './src/GraphQl/**/*.ts' './talawa-api/schema.graphql' + # - name: Validate Documents + # run: graphql-inspector validate './src/GraphQl/**/*.ts' './talawa-api/schema.graphql' + Start-App-Without-Docker: name: Check if Talawa Admin app starts (No Docker) runs-on: ubuntu-latest @@ -351,6 +381,7 @@ jobs: if [ -f .pidfile_dev ]; then kill "$(cat .pidfile_dev)" fi + Docker-Start-Check: name: Check if Talawa Admin app starts in Docker runs-on: ubuntu-latest @@ -401,6 +432,27 @@ jobs: docker stop talawa-admin-app-container docker rm talawa-admin-app-container + Test-Docusaurus-Deployment: + name: Test Deployment to https://docs-admin.talawa.io + runs-on: ubuntu-latest + needs: [Docker-Start-Check, Start-App-Without-Docker] + # Run only if the develop branch and not dependabot + if: ${{ github.actor != 'dependabot[bot]' && github.event.pull_request.base.ref == 'develop' }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + cache-dependency-path: 'docs/' + # Run Docusaurus in the ./docs directory + - name: Install dependencies + working-directory: ./docs + run: yarn install --frozen-lockfile + - name: Test building the website + working-directory: ./docs + run: yarn build + Check-Target-Branch: if: ${{ github.actor != 'dependabot[bot]' }} name: Check Target Branch diff --git a/.github/workflows/push-deploy-website.yml b/.github/workflows/push-deploy-website.yml new file mode 100644 index 0000000000..959f9f8feb --- /dev/null +++ b/.github/workflows/push-deploy-website.yml @@ -0,0 +1,57 @@ +############################################################################## +############################################################################## +# +# NOTE! +# +# Please read the README.md file in this directory that defines what should +# be placed in this file +# +############################################################################## +############################################################################## + +name: PUSH Workflow - Website Deployment + +on: + push: + branches: + - 'develop' + +env: + CODECOV_UNIQUE_NAME: CODECOV_UNIQUE_NAME-${{ github.run_id }}-${{ github.run_number }} + +jobs: + Deploy-Docusaurus: + name: Deploy https://docs-admin.talawa.io website + runs-on: ubuntu-latest + # Run only if the develop branch and not dependabot + if: ${{ github.actor != 'dependabot[bot]' }} + environment: + # This "name" has to be the repos' branch that contains + # the current active website. There must be an entry for + # the same branch in the PalisadoesFoundation's + # "Code and automation > Environments > gigithub-pages" + # menu. The branch "name" must match the branch in the + # "on.push.branches" section at the top of this file + name: develop + url: https://docs-admin.talawa.io + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + cache-dependency-path: 'docs/' + - uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.DEPLOY_GITHUB_PAGES }} + - name: Deploy to GitHub Pages + env: + USE_SSH: true + GIT_USER: git + working-directory: ./docs + run: | + git config --global user.email "actions@github.com" + git config --global user.name "gh-actions" + yarn install --frozen-lockfile + yarn deploy + diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index e45a73527b..7f7328b38c 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -9,7 +9,7 @@ ############################################################################## ############################################################################## -name: push workflow +name: PUSH Workflow - All Branches on: push: @@ -57,6 +57,6 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true + gcov_ignore: 'docs/' fail_ci_if_error: false name: '${{env.CODECOV_UNIQUE_NAME}}' - diff --git a/.husky/post-merge b/.husky/post-merge old mode 100755 new mode 100644 diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100755 new mode 100644 diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603fea..0000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 03d9549ea8..0000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 120e01d8b4..0000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/talawa-admin.iml b/.idea/talawa-admin.iml deleted file mode 100644 index 24643cc374..0000000000 --- a/.idea/talawa-admin.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfbb..0000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/config/docker/setup/nginx.conf b/config/docker/setup/nginx.conf deleted file mode 100644 index 305b1841aa..0000000000 --- a/config/docker/setup/nginx.conf +++ /dev/null @@ -1,48 +0,0 @@ -server { - listen 80; - server_name ${NGINX_SERVER_NAME}; - - # TODO: Add SSL configuration - # listen 443 ssl; - # ssl_certificate /etc/nginx/ssl/cert.pem; - # ssl_certificate_key /etc/nginx/ssl/key.pem; - - root /usr/share/nginx/html; - index index.html; - - add_header X-Frame-Options "DENY"; - add_header X-Content-Type-Options "nosniff"; - add_header X-XSS-Protection "1; mode=block"; - add_header Referrer-Policy "strict-origin-when-cross-origin"; - # add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self' https://api.com;"; - - - location / { - try_files $uri /index.html; - } - - location /graphql/ { - proxy_pass http://127.0.0.1:4000/graphql/; - # CORS should be made strict before deployment (currently allows access from any origin) - add_header Access-Control-Allow-Origin *; - add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; - add_header Access-Control-Allow-Headers "Content-Type, Authorization"; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; - } - - # Gzip Compression for better loading of Static Files - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - gzip_min_length 256; - gzip_vary on; - - error_page 404 /index.html; - -} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f1ccbc4064..0000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -services: - app: - build: - context: . - ports: - - '${PORT}:80' - environment: - - REACT_APP_TALAWA_URL=${REACT_APP_TALAWA_URL} - - REACT_APP_BACKEND_WEBSOCKET_URL=${REACT_APP_BACKEND_WEBSOCKET_URL} - - PORT=${PORT} - - REACT_APP_USE_RECAPTCHA=${REACT_APP_USE_RECAPTCHA} - - REACT_APP_RECAPTCHA_SITE_KEY=${REACT_APP_RECAPTCHA_SITE_KEY} - healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:80'] - interval: 30s - timeout: 10s - retries: 3 - restart: unless-stopped diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..7dd4990993 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,22 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader +.package-lock.json +package-lock.json + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000000..2594cfef31 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +docs-admin.talawa.io diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..4553d05f51 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,176 @@ +# Talawa-Admin Documentation Website + +[![N|Solid](static/img/markdown/misc/logo.png)](https://github.com/PalisadoesFoundation/docs-admin) + +# Installation + +This document provides instructions on how to set up and start a running instance of docs-admin website on your local system. The instructions are written to be followed in sequence so make sure to go through each of them step by step without skipping any sections. + +# Table of Contents + + + +- [Developer-Docs Installation](#docs-admin-installation) +- [Table of Contents](#table-of-contents) +- [Prerequisites for Developers](#prerequisites-for-developers) + - [Install node.js](#install-nodejs) + - [Install the Required Packages](#install-the-required-packages) + - [Install Yarn on Windows Using NPM](#install-yarn-on-windows-using-npm) + - [Install Yarn on Windows Using msi File](#install-yarn-on-windows-using-msi-file) + - [Install Yarn on macOS Using NPM](#install-yarn-on-macos-using-npm) + - [Install Yarn on macOS Using Homebrew](#install-yarn-on-macos-using-homebrew) + - [Install Yarn on Linux Using NPM](#install-yarn-on-linux-using-npm) +- [Running the Development Server](#running-the-development-server) +- [Building Static HTML Pages](#building-static-html-pages) + +# Prerequisites for Developers + +The contents of the `docs-admin` repo is used to automatically create [the Talawa-Admin Documentation website](https://docs-admin.talawa.io/). The automation uses [Docusaurus](https://docusaurus.io/docs/), a modern static website generator. + +We recommend that you follow these steps before beginning development work in this repository. + +## Install the Required Packages + +For the package installation, use only the `yarn` package as `npm` will throw an error. Only `npm` use case here would be to install the `yarn` package. Visit the [Docusaurus installation web page](https://docusaurus.io/docs/installation) if you have any difficulties with the steps below. + +The steps are simple: + +1. If you have previously installed yarn on your local device run the following command to confirm + +```terminal +$ yarn -version +``` + +2. If you don't have yarn installed, follow these steps: + +**Note:** Please bear in mind that to install docusaurus in your system, a Node.js version 16.14 or above (which can be checked by running node -v) is required. Other requirements that pertains to the installation of docusaurus can be found [here](https://docusaurus.io/docs/installation) + +```console +$ git clone https://github.com/PalisadoesFoundation/docs-admin.git +$ cd docs-admin +$ yarn add docusaurus +``` + +### Install Yarn on Windows Using NPM + +NPM (Node Package Manager) is a package manager included with the Node.js installation. It is used for developing and sharing JavaScript code, but it also provides another method of installing Yarn + +1. [Download the Node Windows installer](https://nodejs.org/en/download/) +1. After choosing the path, double-click to install. Then give access to run the application +1. Install Yarn by running the following command + +```terminal +$ npm install --global yarn +``` + +4. Check Yarn installation + +```terminal +$ yarn -version +``` + +### Install Yarn on Windows Using msi File + +Here’s how to install the Yarn package manager on Windows + +1. [Download the Yarn Windows installer](https://classic.yarnpkg.com/en/docs/install#windows-stable) +1. After choosing the path, double-click to install. Then give access to run the application + +1. Check Yarn installation + +```terminal +$ yarn -version +``` + +### Install Yarn on macOS Using NPM + +The .pkg installer can be used to install Yarn on macOS. Using the .pkg installer also helps resolve dependencies since it does not require a command line to install Node.js + +1. [Click on the macOS Installer option to download the .pkg installer](https://nodejs.org/en/download/) +2. Run the Node.js installer +3. Verify Node.js Installation by running the following command in your terminal + +```terminal +$ node -v +$ npm -v +``` + +4. Run the following command to install Yarn + +```terminal +$ sudo npm install --global yarn +``` + +5. Verify Yarn Installation + +```terminal +$ yarn --version +``` + +### Install Yarn on macOS Using Homebrew + +One of the easiest way to install Yarn on macOS is to use the command line installer + +1. Install Yarn by running the given command in your terminal + +```terminal +$ brew install yarn +``` + +### Install Yarn on Linux Using NPM + +Installing Yarn on Linux through NPM can be done via command line installer, this doesn't automatically install Node.js + +1. Run the following command in your terminal to install Node and NPM respectively. [Confirm your Linux distro and it's command prompt](https://classic.yarnpkg.com/lang/en/docs/install/#debian-stable) + +```terminal +$ sudo apt install nodejs +$ sudo apt install npm +``` + +2. Verify installation + +```terminal +$ node -v +$ npm -v +``` + +3. Install Yarn with the following command + +```terminal +npm install --global yarn +``` + +Finally, after installing yarn and confirming that it is installed in your computer, run the command below to install the packages + +```terminal +$ yarn install +``` + +# Running the Development Server + +To preview your changes as you edit the files, you can run a local development server that will serve your website and it will reflect the latest changes. + +The command to run the server is: + +```console +$ yarn run start +OR +$ yarn start +``` + +By default, a browser window will open at http://localhost:3000. + +This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. + +# Building Static HTML Pages + +**In most cases is unnecessary**. Running the `development server` will be sufficient. + +If you need to generate static HTML pages (unlikely), then follow these steps. + +```console +$ yarn run build +``` + +This command generates static content into the `/build` directory and can be served using any static contents hosting service. \ No newline at end of file diff --git a/docs/blog/2019-05-28-first-blog-post.md b/docs/blog/2019-05-28-first-blog-post.md new file mode 100644 index 0000000000..d3032efb35 --- /dev/null +++ b/docs/blog/2019-05-28-first-blog-post.md @@ -0,0 +1,12 @@ +--- +slug: first-blog-post +title: First Blog Post +authors: [slorber, yangshun] +tags: [hola, docusaurus] +--- + +Lorem ipsum dolor sit amet... + + + +...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet diff --git a/docs/blog/2019-05-29-long-blog-post.md b/docs/blog/2019-05-29-long-blog-post.md new file mode 100644 index 0000000000..eb4435de59 --- /dev/null +++ b/docs/blog/2019-05-29-long-blog-post.md @@ -0,0 +1,44 @@ +--- +slug: long-blog-post +title: Long Blog Post +authors: yangshun +tags: [hello, docusaurus] +--- + +This is the summary of a very long blog post, + +Use a `` comment to limit blog post size in the list view. + + + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet diff --git a/docs/blog/2021-08-01-mdx-blog-post.mdx b/docs/blog/2021-08-01-mdx-blog-post.mdx new file mode 100644 index 0000000000..0c4b4a48b9 --- /dev/null +++ b/docs/blog/2021-08-01-mdx-blog-post.mdx @@ -0,0 +1,24 @@ +--- +slug: mdx-blog-post +title: MDX Blog Post +authors: [slorber] +tags: [docusaurus] +--- + +Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). + +:::tip + +Use the power of React to create interactive blog posts. + +::: + +{/* truncate */} + +For example, use JSX to create an interactive button: + +```js + +``` + + diff --git a/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg b/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..11bda0928456b12f8e53d0ba5709212a4058d449 GIT binary patch literal 96122 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 literal 0 HcmV?d00001 diff --git a/docs/blog/2021-08-26-welcome/index.md b/docs/blog/2021-08-26-welcome/index.md new file mode 100644 index 0000000000..349ea075f5 --- /dev/null +++ b/docs/blog/2021-08-26-welcome/index.md @@ -0,0 +1,29 @@ +--- +slug: welcome +title: Welcome +authors: [slorber, yangshun] +tags: [facebook, hello, docusaurus] +--- + +[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). + +Here are a few tips you might find useful. + + + +Simply add Markdown files (or folders) to the `blog` directory. + +Regular blog authors can be added to `authors.yml`. + +The blog post date can be extracted from filenames, such as: + +- `2019-05-30-welcome.md` +- `2019-05-30-welcome/index.md` + +A blog post folder can be convenient to co-locate blog post images: + +![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) + +The blog supports tags as well! + +**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml new file mode 100644 index 0000000000..8bfa5c7c4b --- /dev/null +++ b/docs/blog/authors.yml @@ -0,0 +1,23 @@ +yangshun: + name: Yangshun Tay + title: Front End Engineer @ Facebook + url: https://github.com/yangshun + image_url: https://github.com/yangshun.png + page: true + socials: + x: yangshunz + github: yangshun + +slorber: + name: Sébastien Lorber + title: Docusaurus maintainer + url: https://sebastienlorber.com + image_url: https://github.com/slorber.png + page: + # customize the url of the author page at /blog/authors/ + permalink: '/all-sebastien-lorber-articles' + socials: + x: sebastienlorber + linkedin: sebastienlorber + github: slorber + newsletter: https://thisweekinreact.com diff --git a/docs/blog/tags.yml b/docs/blog/tags.yml new file mode 100644 index 0000000000..bfaa778fbd --- /dev/null +++ b/docs/blog/tags.yml @@ -0,0 +1,19 @@ +facebook: + label: Facebook + permalink: /facebook + description: Facebook tag description + +hello: + label: Hello + permalink: /hello + description: Hello tag description + +docusaurus: + label: Docusaurus + permalink: /docusaurus + description: Docusaurus tag description + +hola: + label: Hola + permalink: /hola + description: Hola tag description diff --git a/docs/docs/intro.md b/docs/docs/intro.md new file mode 100644 index 0000000000..45e8604c8b --- /dev/null +++ b/docs/docs/intro.md @@ -0,0 +1,47 @@ +--- +sidebar_position: 1 +--- + +# Tutorial Intro + +Let's discover **Docusaurus in less than 5 minutes**. + +## Getting Started + +Get started by **creating a new site**. + +Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. + +### What you'll need + +- [Node.js](https://nodejs.org/en/download/) version 18.0 or above: + - When installing Node.js, you are recommended to check all checkboxes related to dependencies. + +## Generate a new site + +Generate a new Docusaurus site using the **classic template**. + +The classic template will automatically be added to your project after you run the command: + +```bash +npm init docusaurus@latest my-website classic +``` + +You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. + +The command also installs all necessary dependencies you need to run Docusaurus. + +## Start your site + +Run the development server: + +```bash +cd my-website +npm run start +``` + +The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. + +The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. + +Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. diff --git a/docs/docs/tutorial-basics/_category_.json b/docs/docs/tutorial-basics/_category_.json new file mode 100644 index 0000000000..2e6db55b1e --- /dev/null +++ b/docs/docs/tutorial-basics/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Tutorial - Basics", + "position": 2, + "link": { + "type": "generated-index", + "description": "5 minutes to learn the most important Docusaurus concepts." + } +} diff --git a/docs/docs/tutorial-basics/congratulations.md b/docs/docs/tutorial-basics/congratulations.md new file mode 100644 index 0000000000..04771a00b7 --- /dev/null +++ b/docs/docs/tutorial-basics/congratulations.md @@ -0,0 +1,23 @@ +--- +sidebar_position: 6 +--- + +# Congratulations! + +You have just learned the **basics of Docusaurus** and made some changes to the **initial template**. + +Docusaurus has **much more to offer**! + +Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**. + +Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610) + +## What's next? + +- Read the [official documentation](https://docusaurus.io/) +- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config) +- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration) +- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout) +- Add a [search bar](https://docusaurus.io/docs/search) +- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase) +- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support) diff --git a/docs/docs/tutorial-basics/create-a-blog-post.md b/docs/docs/tutorial-basics/create-a-blog-post.md new file mode 100644 index 0000000000..550ae17ee1 --- /dev/null +++ b/docs/docs/tutorial-basics/create-a-blog-post.md @@ -0,0 +1,34 @@ +--- +sidebar_position: 3 +--- + +# Create a Blog Post + +Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed... + +## Create your first Post + +Create a file at `blog/2021-02-28-greetings.md`: + +```md title="blog/2021-02-28-greetings.md" +--- +slug: greetings +title: Greetings! +authors: + - name: Joel Marcey + title: Co-creator of Docusaurus 1 + url: https://github.com/JoelMarcey + image_url: https://github.com/JoelMarcey.png + - name: Sébastien Lorber + title: Docusaurus maintainer + url: https://sebastienlorber.com + image_url: https://github.com/slorber.png +tags: [greetings] +--- + +Congratulations, you have made your first post! + +Feel free to play around and edit this post as much as you like. +``` + +A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings). diff --git a/docs/docs/tutorial-basics/create-a-document.md b/docs/docs/tutorial-basics/create-a-document.md new file mode 100644 index 0000000000..c22fe29446 --- /dev/null +++ b/docs/docs/tutorial-basics/create-a-document.md @@ -0,0 +1,57 @@ +--- +sidebar_position: 2 +--- + +# Create a Document + +Documents are **groups of pages** connected through: + +- a **sidebar** +- **previous/next navigation** +- **versioning** + +## Create your first Doc + +Create a Markdown file at `docs/hello.md`: + +```md title="docs/hello.md" +# Hello + +This is my **first Docusaurus document**! +``` + +A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello). + +## Configure the Sidebar + +Docusaurus automatically **creates a sidebar** from the `docs` folder. + +Add metadata to customize the sidebar label and position: + +```md title="docs/hello.md" {1-4} +--- +sidebar_label: 'Hi!' +sidebar_position: 3 +--- + +# Hello + +This is my **first Docusaurus document**! +``` + +It is also possible to create your sidebar explicitly in `sidebars.js`: + +```js title="sidebars.js" +export default { + tutorialSidebar: [ + 'intro', + // highlight-next-line + 'hello', + { + type: 'category', + label: 'Tutorial', + items: ['tutorial-basics/create-a-document'], + }, + ], +}; +``` diff --git a/docs/docs/tutorial-basics/create-a-page.md b/docs/docs/tutorial-basics/create-a-page.md new file mode 100644 index 0000000000..20e2ac3005 --- /dev/null +++ b/docs/docs/tutorial-basics/create-a-page.md @@ -0,0 +1,43 @@ +--- +sidebar_position: 1 +--- + +# Create a Page + +Add **Markdown or React** files to `src/pages` to create a **standalone page**: + +- `src/pages/index.js` → `localhost:3000/` +- `src/pages/foo.md` → `localhost:3000/foo` +- `src/pages/foo/bar.js` → `localhost:3000/foo/bar` + +## Create your first React Page + +Create a file at `src/pages/my-react-page.js`: + +```jsx title="src/pages/my-react-page.js" +import React from 'react'; +import Layout from '@theme/Layout'; + +export default function MyReactPage() { + return ( + +

My React page

+

This is a React page

+
+ ); +} +``` + +A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page). + +## Create your first Markdown Page + +Create a file at `src/pages/my-markdown-page.md`: + +```mdx title="src/pages/my-markdown-page.md" +# My Markdown page + +This is a Markdown page +``` + +A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page). diff --git a/docs/docs/tutorial-basics/deploy-your-site.md b/docs/docs/tutorial-basics/deploy-your-site.md new file mode 100644 index 0000000000..1c50ee063e --- /dev/null +++ b/docs/docs/tutorial-basics/deploy-your-site.md @@ -0,0 +1,31 @@ +--- +sidebar_position: 5 +--- + +# Deploy your site + +Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**). + +It builds your site as simple **static HTML, JavaScript and CSS files**. + +## Build your site + +Build your site **for production**: + +```bash +npm run build +``` + +The static files are generated in the `build` folder. + +## Deploy your site + +Test your production build locally: + +```bash +npm run serve +``` + +The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/). + +You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**). diff --git a/docs/docs/tutorial-basics/markdown-features.mdx b/docs/docs/tutorial-basics/markdown-features.mdx new file mode 100644 index 0000000000..35e00825ed --- /dev/null +++ b/docs/docs/tutorial-basics/markdown-features.mdx @@ -0,0 +1,152 @@ +--- +sidebar_position: 4 +--- + +# Markdown Features + +Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**. + +## Front Matter + +Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/): + +```text title="my-doc.md" +// highlight-start +--- +id: my-doc-id +title: My document title +description: My document description +slug: /my-custom-url +--- +// highlight-end + +## Markdown heading + +Markdown text with [links](./hello.md) +``` + +## Links + +Regular Markdown links are supported, using url paths or relative file paths. + +```md +Let's see how to [Create a page](/create-a-page). +``` + +```md +Let's see how to [Create a page](./create-a-page.md). +``` + +**Result:** Let's see how to [Create a page](./create-a-page.md). + +## Images + +Regular Markdown images are supported. + +You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`): + +```md +![Docusaurus logo](/img/docusaurus.png) +``` + +![Docusaurus logo](/img/docusaurus.png) + +You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them: + +```md +![Docusaurus logo](./img/docusaurus.png) +``` + +## Code Blocks + +Markdown code blocks are supported with Syntax highlighting. + +````md +```jsx title="src/components/HelloDocusaurus.js" +function HelloDocusaurus() { + return

Hello, Docusaurus!

; +} +``` +```` + +```jsx title="src/components/HelloDocusaurus.js" +function HelloDocusaurus() { + return

Hello, Docusaurus!

; +} +``` + +## Admonitions + +Docusaurus has a special syntax to create admonitions and callouts: + +```md +:::tip My tip + +Use this awesome feature option + +::: + +:::danger Take care + +This action is dangerous + +::: +``` + +:::tip My tip + +Use this awesome feature option + +::: + +:::danger Take care + +This action is dangerous + +::: + +## MDX and React Components + +[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**: + +```jsx +export const Highlight = ({children, color}) => ( + { + alert(`You clicked the color ${color} with label ${children}`) + }}> + {children} + +); + +This is Docusaurus green ! + +This is Facebook blue ! +``` + +export const Highlight = ({children, color}) => ( + { + alert(`You clicked the color ${color} with label ${children}`); + }}> + {children} + +); + +This is Docusaurus green ! + +This is Facebook blue ! diff --git a/docs/docs/tutorial-extras/_category_.json b/docs/docs/tutorial-extras/_category_.json new file mode 100644 index 0000000000..a8ffcc1930 --- /dev/null +++ b/docs/docs/tutorial-extras/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Tutorial - Extras", + "position": 3, + "link": { + "type": "generated-index" + } +} diff --git a/docs/docs/tutorial-extras/img/docsVersionDropdown.png b/docs/docs/tutorial-extras/img/docsVersionDropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..97e4164618b5f8beda34cfa699720aba0ad2e342 GIT binary patch literal 25427 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 literal 0 HcmV?d00001 diff --git a/docs/docs/tutorial-extras/img/localeDropdown.png b/docs/docs/tutorial-extras/img/localeDropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..e257edc1f932985396bf59584c7ccfaddf955779 GIT binary patch literal 27841 zcmXt9WmFtZ(*=S%B)EHUciG??+-=biEVw%f7J?HT77G@f5ZpbB1Pku&vgoqxemw6v z-;X&{JzZV*cFmohnLgcd+M3FE*p%2vNJx09Dhj$tNXVWq2M^|}mn)^e9a~;bs1CC4 zWs#5?l5k+wXfI`CFI{Chq}oa9BP66(NZK0uiU1Kwn&3K0m`=xIMoxdVZ#+ zp?hKSLSSimjhdEzWp#6Tbpr;2A08YY9vwczVR!d;r)Q^kw|6h$pbtRyO;c2US2)Ho=#3q?{4m1GWOCI`k&9;zl9YDhH|l{oVck{{HdF$xGeh(%RX@ITa1V-QE4arPZ_3^N0KUo15FS^Rt74gNyU?f6HsD z>zmu#+n1LY=NIRf7Z*oIN2_aF7nc`%dwaXPyVf>#Q`56+>svGPi|1!&J3Bj8*0u|a zE61nDOKTge8(T{&>(jIU{?5$PF)%N#t}iaHQc%;Ky=4F7L{Hzy*Vp$Mj`%zGZ+7k< zCpRC^+V1HYCi6}{?rS`Ew80CL%d5-LF)(<1lJAQ_QE}I< z?$m+XE%JR|)Y|g5*Z=3YjLfXkvht|tSaC_|$oh1*A78S&%grr-Q|oi0ai*n%^?I3Z zz4Ifn)p1zW0ShuJU zjT*W!;4n~Y)3m5E=4m0n9;cN(k*j`y5!~j2)ij4x1#tx zB&it>z`(yY6BF>DU9?)rvOb2G!4AbPa`$!ju_}{}N=X3%ljy@XN?Dz5W~L8#vn;(% zS0y`!_FK8bT{5iuza9iPzyFntcC0hEUgCyxwZgrs_lXv54ZHujy!d4_U`~v!&Xq6w z_%CfMkDLt!D3SDYg>XEZ!YJH*s~-dg$LmS&Mt_;Y7X9a!>IDr+ded%2&q%}2^ODhk zoJMHe1;<*D7+WnelW=pb#;#*9m22_D0Uy+B;{x z(r=4T(e9>b$HL=1ZhtTnMZ8m?T*4WlE1nANJoY~M+S`a~oAzPxq?IY|K;|faC(Qf6 z6st=g2Oa&+>GJF*AU5<{Q1pIIjk9IOz}i1XThs0R)dBg}u}I!L^(JejuqE{$Bx0WH zK_L%2hekVKCo%({=C&4>8XPbm?HVjtj7;pR;Nl%bO7u_%gfl5w5S;(8b>qCb9KY=2 zcH1B8#T*pZQMR+_zF|mDvyu5p%arE^>?K|9F#FDuJCyu6$KPjjPBMq7j0f$|h@y!QXH+UdeH3iv*9ArYX^V-S2rxolaBRROkUH4!AxVghY-$mqUuOg%w5X}J1K z3LIKED&GtI+|Bu|l2OgJXS@ z##5m-UU-??q5BVBs3e%jt&;*!MXilSO_r%{gmW&qj$2WWx8M1Us?Tzp=Of?r=^y=m zDDr>5Z2+yUUf9O3Kqm?KxT9VJX#G6EP&E+e7EkxJF5QqcBPy@TsIFiD!!LWKz2ftR za<|^DinsXw>aBe|0DWOEi#5cV&B>!$i8?+vTr3ZDMK}XFeg)Ime5=*V++LLjj6sSf>5d+I|6V|cU`LfQPC z;p|(TN|j&~8CO`*qIi-79281;uL=cj-kt$ zx5MwWh>2LRlqjdUEGgk)P@$`Rs3-3sSlqxdxpG@!K`;a)V2m#wvau8$FIZuT9T00v znI8L>LHCkAZsu+5PUedUKs5fY2Ehv7Lqr}Ue$h;p6jBeeweEDUn2p#fwkvxk%Z<-6 zlgcD$>a-9H1#>^}Ku>>wLa`FkP^$V?ys$YQ&1L$o#0R}|{e?+I{K?~0CPz_*Bh#mo zh#!|PeV|ebfXa=JD#~>$?!*)i)b@eZZ`$qTk#-n$b{Cnhx2wH9N;PkqOwfS5FPe4A z!^5G+7=f|QUkN8gZmRRF-gxA&%`!7|FLGzf?uPu9E>P4d zrO@YSB$ z8Q{^@GSty5G&7xHSPy#pErSb3Yym^l5+QhvVlc)ItslUVgKOTQyYw8QX+2%`A%uhb zCJ{CE9{zUB(&-v8uRN|49S2Np{L4XRjFWz9R?)%ikl#d@WJtzM$=odVE^A1_CR5$l zs~b7y&?qM}RqSq1_-7&^wqiGh$yZuM2alHG{5LL=^QiF^u2prn!rcZ9%AF_!mJaxS9)8?8ha{9;`m^(Fx7`o(9*^- zI+OEv7<`;JEbKrNAh#EhBOA3x9E1Hr;lS)5pbY@p_LBMGn<&!Nxl41i9>dX%V}P+N zR;}+{G5WqCjnW#@f9ZNd^d5R<+ViQpx-L3$P}Nkiph3->K~K9)Sw$@INj*8YJLj@f z*+Rh+naB!_+NtSnzwWfLhq1;bmSozM80Xik(oGSLM*c)>iC_Wvd=JP|df1=roC3iU zoG&xR@$6d-6s0^VR}3V5OFQndgqfbboOay9Tf7RQmygGWgZ+DD(=|p9Aw+)O_j8?HRA#~+mIn^!H zQ6fcNW1FIjQ#SN_nK%EQV_F{VV77VfT5B(ea{vC|K#&-RTdcH#OR%(Mr#R1?jLzzq zSC-hN{(b^Ik^Q{uB|gq70;JUnM+#nmHCHA@PxC-sYqdnHZfEu1VHP*(8?jf)TsXH7 z`d(w{qU>V+81-UywGHL+AD7SV`|6-5PENL9RC02nnu15q_;*RRA_g8|!M(z88r&2? zCYs;1K=%c4QceJr-h+O=+K2tbY%HGQfyO1=9--HP5(yo2@2ad|TVK+$67(dBRpKI9 zcTvYDh?n^D9&qCvQhZoHb7DSvql}UJ8B+>~m5-ISatyypAR9WnfzbiDmXq*ctR3Xu z(~YwCAKYipx{EI8!HwsIlC6i`0rhcb>6<%+Cp)h@mK*_1d8_q6dg4>n}&ihP)NGiUvb81U?bXk&I< zbcqui@YB^CK-jFfu@*XpEERc^Mh(aJ)LBA@| ze4m|#Gs|Rc+0u4VvgE2s^$ ztYjCc@_u6&>iu~fe+ed*pr>hTdj(LcVf&SE`t2uXleZ(mhZd7kd|U$5HrJHPQ@IZ7 zz1w#&@Hi?VMVg$?DV~d{6LYoL8SFlWmuiYZxE8-M?^q32JSt7GoOVzZ8#I13;Ax`h zy=DXkH>H2B>%O@Ual0AO#Lh>Z`q=%r{iaZi3fZKcmBtmff&=e!GF%sO1~^L| z<3g?B>etUeZ?Suv6A<@bH;i=|KtG0mk@t4!qPRX4+^*osf+?77qg=U_OjVUxbTvh% z8DC!P=LlXRVFEd#m0i*Ka(b7e+3E&CC^Yv2#TgpoU(C>Wsp4))0%aRYtPxSr1x zO6uJUAMROWMj1L@;~jX6gRh(+e1ZqC_CTY4s&GfB-E;b?6+vEb;^bSE6j9xTFW;oq z9(1ndc$4}qdAB6ta4BN@p|T{**jB2P48}=Ya*Jc5#3mv|J&XRD;~yH>^DLwT>bp@)BbsVm+*3t=;598_Aj{ zF(?v`d_@ky*e%9dvu#A7+LtE~P$5VDCRJz{ZCt3Qh5aQ==>mF~k7bTCZxZg$!jnP8he7?WmJYT*1>c{*tJR|Ie+ScEevd4@gG>!gnL_ZL0 zKC)4$4wIXHIG~yE4+vZ~gh~Du9&92xJVUy91zt6P+$SZ9%)_wNU7KW~uGu2PF`KM6 z)UjHJQr%bRkMmIKABTD;BRcKhrdAbU;gFURvdg`TDW)T{)k8(vFbmtSAMueO{E8RHEQz-$F2C0;smk?8Q*e=qM%6O z6aGCJV;h1Tf3qvPEYi~fsz?&nlrg71v(eKqA!&F7d&p(^Xy#{`bl-!6%zc6pwsB;^ z+s#(uj7tu(L!ti&l1T51?Zuxg`16)sS-XNZm6tV-9#MfVeX#M39*XRuyFiJrxU@lO zA94#H%u0U~Ea9b26Qf{o;FeeG*!6uF*bYv#%%B^zN~9gqX{FS&&Ba|4AuSA${f^sf z7tg9}O%6m})g#&j5f%_eXA&}AZI!vQtzb=^sQxVZi~_}R^pgdM?5WD3%5Gx)%~qaP zgb4y1pEi3Ut}qG#QQ8SxhEkYe1Iy%QMz~|VS zKNsn5WGa%en;uc#7;LpDxYo4^@zL&dT*?Movr0f}Fry~2?+=LVy&$9SKV5+@SE-{M z4E!tmqebqFV%O~LO=L7??~zNUu90ECkq2Dut+Q$C#QJ*uQ33)=L?sH^oM|)e*HvE5J+C=qp79zhoRrLcNRA%1 zo?(m~(so82vOoC7`kQMWO5~^(`_b!C)8yq_VgnO5blD*sV`=DhQ}{$VtHxJJ@hixJ@hcZ z!Y6lPxZ6KphBnMJ)Ki2qFXY=iKs$GnX#1@Z7~hW~TuZju?)u=y?>z5W?Gv0-coA#k zCeo>mYl2HbT(xw!L&23l5KXaDk)yq}eBc&oPdWOPI`+f_o2cgW5QeU+)?Z2SHRplP z^{WM#a*z=ndtAjrTjbW0xE@*Ir~X+Bi-n#;6t1um9|^H4v%4b8X{_t71*TeupTOxB zM!=Yir}l!cM!GzQSnjS?@tOr){-JXhj8oH5p=g?cX47@jYyLLVq#|_Nsv3>>?X=ey zqHoKr;KTdI-GBAo?{+YUsVsacvsXS>8d?dLdU_)>MB*glDaE}%bBrd^98i+k4NQ8s zc0?8Fbqr&)Wq3Wd=YVyyUH$oZkbSRGYQQj1NofbRth{_t5aE##Z zRgYXbJ@On89x{nXLRlW`84WcfoXw=cPcZZH9T^b zcb#iuU7-qyv~G@U`}AkosbCYozUSeB3Hxyoirpqhcbvd|soGDf8>z48$4OE>XaW4E zM`Bd>uV&vA8~mC0n0*yWn z!;O|1HnCN1ghEB898BR#@4Bo&&oP9!4dcdtLZ@`un@&0 zzvF-GJhEY|FLF{hrM=dB7|h@3bEZZVJc3@GCJk0{ONwS8^g2F0`roJtV2uvN1O)|| zIfYh)=}lZzT`5BbTHcM6zo=WwB7-gyvx+Cm)a}&MT+1M^^h@h5kMVlZF*~3?Y5n)L zG9~s#<;5)1%>+_Ny*GZHAebop+bfp3&+eUH&4)I7Bc%5<40;DxP0G8{l|7Ufj)b!u zw?zWRNHyLJzYlCQj^pLwN#g~68@bp>+KA=l8QJkW-|B;3+XPeez-@9TIs${Q*6_9g zgZY+gF6*%)arn3AJUkn5bhfZ9zut{n6VIK=XKt|=rtOVmc&6zImd8%#b}Bw)vQ<=y zZ*)E`F>yPlf=T61Cm%u&Swgy**c63kVp0V|yM7_vkz7jkw+1H3?_NcbXa2QR`&1S! z+&YBgY5aZe3Oz3Y&y0-J_SoE$OJ?^Y5E^umyENba+t#hf=fjWb@y_QD-S_*?k6rg& zYCqi76Dk6v!l>?hqKLvuFrKkCcX`eYORriHtB{LekCARf*i6xO%HyN*j5mwg%*8!T z_-nF5R#R3`E%JC%un?Z*bLKZbmC(`y?h5hS4~y5*hgyC*ji|t|>+*|`-dcqG*G|Tt zEST8(?OF|TW>rp<0OymrGE9zAlwD*|y}VO>>~H8Z91s2Imik`Rq+^-6$BW;-O~_dA z!0~$@ir)8VZEok*1Z^bx^25FUR#w|5ZBYL3o!iz3!TIR!4dM0kJ3M$Uu6oT8;CKYy50-UD6m_X=r8s9+5$+sA0zy6pqH_&Z@W^+??+HTsDpji* zpJYPs-t|l<_3g9}ngwho*oRGjLvmgR^?mB%vOAB;nrI30-@eap3v)1iCsy6LJHpO1J< zyJZ4Wh4TL8e$;A)3J{xrvG(WSc=))?Jb7Ude7PQzrs^QKFUs80=y)usVamepIs@|w z`Iz`#mm;4!p8c?~+N=@YBv*C$SE3I503HJZ0R|PT!IyVtgvYdpEy__RjV?qXKeZS8 zQn;w-0EHEP$J1*7n@+9+ndkivReVrStsXO#HIyz74ueJ3uc5Y(sVEe}?RntR{lQiH z`Z!qQ;Og%AD&~>mulH;=Kz}3H2_E@LZb@~4srs2{vY?%@)Kl!Nap4D79D{9}Z!`{& z?#?MOm>og((zofbkjOl>6O9@pvqoooVcjc^C-#xV?L|D3rXAR!rX4PzRkgx;H70*D zI_Pqi!x-h~CVp;&e0Ji8#XXONI@+S1=SSfqMQ>WVhhw!ZpqKaFLfG@O*E!;9JweoR z?{TX1XS6B@-~)hQV+wZL_soD`{+?KKnJh{Y4z>ugj&n-b6_}jBe(jSLX6P z&9H{W>AHrLNjvzbPKRmV@tT%0mYUCuBT1kvP^GO=`ICpra+8UwYXrd(pWPuzm_4{& zWk{u~y0Zv8Qlt(vtPO(#zX5n?`VDW3Ct(plTSM;$<*Wqlw`Z7-AN6CITh2!btkaDu zrf!`e&u14f%tSP&(Dnr<9bp(XcXW%tYO*s963nBWA=#0746gunNA6vAeP1s zh3fwN_Xo-D)nJ}kr8L9iLhlp8zQQ{nY4Q$@E9VtETvY3caFqEe?wB~cpWg4cy=Whdd?Z? zXPs;EKDvGsP6*bHo;Asedj+UOAyPE`Cwl8av`E7KMRPx4{M5Nm)na^3~o1fyYQucv~N{FBO$#$%a?f> z_2b|tKXBB$5)5npHFNe?Zy-grTI8sM+$}L__i>e2nemkwx%9r!i}lDhBEL!$_8+d6 z#LJ6vr&OO=-?Wf@W*)yvCLByyX|NQV|ecCy7=VAOB)9BI*Nhl6$m2&;G5gX z7X%M-WD-iH8(`K^IByV*KC4pkE;Q%d_{*#4?^g1OlJz4do+x=4js7@ z4A1i5J{^EH#kWeooG$|j7@#2|@kwpNNOp2q5tS?TUv|0sCwg@^U#G?D|NVyEHk3@4 zh9QWPx@!?z6UooVSfd6QY0LCJiII2vLNZ0~Jqnz~Z^l-ou^A;QU;}AhM{s6oqmA>R zx?|OM=&u!W1Uio$0m&-Ry7O|=MSkJHZ2nMCm3cd2v986rcYhXj>{)~`rp~In^`jTf zFrXGkn7tKYRu$h+~JfC4LO`D=-Is- z`O52#2dQHUn`kg1yFQXPBn)1doD3>%Z#Qc1db!Om^YRfrJIQst z-;fRaT=uTy2I$-qS|{FdP~V|NDf7ik?ZkYCef!_RSVV*5*a4(SshTJnq8S~a`-xao zsx;}%hcFK5ULvK;gHS_-z^^qx#frvEWpEI~{rtfbuS8wSnx+wfU>o`2dC=x3`D zBhoCot?)M$PTo$u&5L;JYCKUEb(v4VM%h4az4C?X?!Y6cb3KdhwS}?e9dC7;HdnO7P%wI_DM;;s)@@Z%bXbtAz>;d_JUlP#%eF{9 z&G?mfv!)Kp4BGm-`S$V!e>YW%_7wOu6Y@dH03UOV54u#?t3zN87%+2DV4y8UA)tjRAF;L2r0P4{}i zS>CSrwAQsVg`0^P+-P9(t8Inr_eUS#5t?4*HluhdNj63cJr5&s250OW1_Y*Veacuo z)0zW>;IdzS14@>TV9}D^5NujBuLsVE+*^zGaRsMzd40GW&lUtN9c}wb{~oH-rn5i@ z8}x~^(V56NJ>0RjWulsd{#z*g#MP3;$Kift?|Xb^>Pq7n-uera3;fa&%Kqq+sTISU z>9I?T5p%nzkJI+%EB3-pvu^_`-K4BPitQJr=<|A1pF^2$^d||Im4!Lx+DZc#;0d%Z zU}NxmZU|4p(!59eAHdzA{rqw6Ka=ssc2YVTy@Kr%TweSx7~PHI0$Ux(MH2xP>83k; zbDo^brmW`!))Eo*!~#*~(W4nwS!=Y1;yzh_{9+ERu~TOO)jk9Zv~B;)rYQX6mHFEK z$FpwAYy(lY1r9y+I7I{>9?geW)UF1iXT09htM#|*5w)gCZMKyi*_Ji;8TO`jkr6_D z6d^;@Cn2~1@1t9zQh@LC&YnCIm}xot2eOM8;p8qUQN8+;{_dBN&^VM~s_~5G#LV6m z_E3xKqtq!foUe8JYAMWpG6L66c?}#MBe-snYIx34#${6zQ+joY8Si;6OdZ&ke9RI9 zhJVE8S27lRcxM1to&zo06ulR~=)s2%EoSb-}Kq8vZm%56`3bWG&{95m-EEyf%f3 zH>Hp1P(-{>oBt2RmrZ0^^02K|$)u`-lkn!CnYo`C98s@Jf)-Nt3YGS7qu+WJ#ig-Q zFrQrF(9BS8SkgJ;+Ad7Nb-pL%EFha^nT1{-?E>u#tIcaiqZ19=37#rTd8pgB7g#`{ z3R`W-FmER}xBCpl>6-zNKPtsGV+;sy5|;j2PzH**0v8xbiA$I)z;nGF=f0kD;9o80 zk9RY17@+hFh@PzHbGN#U;3$|?cr@7<-4>(%aAapZ`iHIwt+VtBy0LH(1}{C)3kg3a z$axD|Iyt-X`@2lAY5noiw7Ges2e_Qy#ZG7g7!r}~R1hs0kXTsZV6s<#V!mFs#>11$)A=<$Kuz z!efePeRv291X1dfQaDLD&pz&rySTeJ)gM_}RHN4$p39$|V&}Hy&}+?dW^|({y!MySY<7Jzg!O zf^s9Ppls*TLgM-SI9c;jdIIB_?_E}SC2dbL5<#e@~e!>h*T}3V7Qjuwb}kpd$k{i8yIhNxcWp5 zmhr}|T%BZqGQI3rUBDr76MVryhwI4_s>U>$O&%JFqpibpT73JynWfVyP9vAd8#TkF z@b21lX~Xp&JvEw!njH%gzR#bLZ(HQc-x>V%ncNiNZVJK&R)GfUJ{=r%@BYj|e?tAE z^QvUXJVicpo4=Ku(9&oBMNT}AFs6q4)YmcNKs}&Yl3qAPrANKvAX)cQ0-_JnGLH^% zib2!LEZ+!2?9Xjt;Vsr#lw0vn26t$134ju@;-k>6A|D<1f9{NA&6lpAq^(bHU;73`4+N|^gyuiqNV6V>4tiHuh2}gS>rpliJMYF> z8oV`hL{!l3Cr!jFuS`U(PLYOcg;mf+q*tapy-Rrq73i4^Zr_D8w5!nj+I0u!FF(jA zaa|Fie9MYyVD zY+|f$aJ?0^#q(7Bv(_Rf>!-!26{dkm`vv5_{yhqlfE=-JnrnR3CE&==9oG^BPJ~kT zwR#L%pm6XWo_o>~-xFwsnFCS-K3SEG*9n3OmOIw$y|;&`Jh_54%d_jy$;Tc2Y_spR zsaIH2IH@qw%s;q1T8%_~*JZ&ytt);Fy%vh>g z0w_CsOn#JW{R5GsH?OEs1xr47FZzM7B-{&lNe2bAnJ#CYkWk}CK065tB0jzXv_Ue+ z&!kU}(r(0*6z9AtXe^RO8lX0D<%I!#-wUlmC}2X3R^;0)cuXyXl#01U9aAYGBNq07 zQ0C`^>CvlIsr|X$a@#JlI=!B?psUQx$bJ$^?{z*pe0X~bm^`c#V&s{0MlZ2T-y>}F z;qPquk(Pkc+@>~ButddAyRL%Hp<*0=QjboBwPSW-PHOEB-@Y}(p8aa|yNnqY5iwd} zMW09Non<@D_S6*Yt^2H1H_*KaVR?1$sYP$fe%28z_TYR*uvmX_{;5wg$t{cwp()qhVL2-qx3)1wM*a1-Qko7WOS|m_n5#TglB_)$&TDF_|oOK~F z5`+$vb~~{DgX@<_1p#;oVwb#0EZ3TI6$r55L4sS>BE@dTA#G0aD>84pQZg}wEWXX` zi!o|(wQ#4Y+7TC_zH2&(JiwOOYq`B)ZMOS$()lGjP?Re|ONa!QYMvwZxST#y zqxy;V%ft%25Xi@T@m(kD!pOvW$-@7ISP-Y%N|Ru>0)+_1!Xqh6yx_LcFNm{O`PE!f z1~@)qX~N_wIEb^f5u-?lm)di~;Jr!!^i2p381+NQa^Cc41Q-KE0Pi#aTB>o!<@$c% z*Q&0@cBXHDTZ2s@7*To0m*BYhWJwxEsgU+sx@6~uz6~lY%RS;a{p~AC-LG>IUop{T zr=uIPav^B@XZ77ba;qQ)w|Dxt$Q-fY!I+bh=a*g~Nhdb4cY<~1N)F-&Ui>SR1l(Zm@ zU~{AX%FoF4u=?X-SNV(5k>HE$9dJyNJ1i`5o7!u7exC)~47YqFkDvB6Qvg#`GnW$m zy^C0qY~lL3`HdJoR6L$C-K(+><84eipiDHzaN)Qv$Lvk($43+H>IVoTphDA%<1OV7 zN*wIOIb>eQ)`8RyzvwEjennj>vn!@tYo7b3bB?40+SdR)E#yrS^OTn6TmN05HqK%l zP)ZuCwf1Dqt9nt}M75{7)xl28WCdmP&nv%F5L&v^Csh6lR4+6qW$%QBQl1y9g2m&zLQodlxDQe5t ze74A-pBpIlCOSp+vzs<1{?Jh<5)t`U7lpH47Ax0o_SFnzt-ale`H{M8h&qB)qshbx7Ad#HNB$| zo={%npyBI&{m}+3+ngQmW@l~dYovp+my{i|_PyEoYucnl>EfHm=~;&)!6SYGXW9S; zu#fmK+2v+_G46lfe~J+}-wMrzj+?*^#t`G>E$l*-E7%bPB)Ef578L#cU|%dTi4@hk zp;+bBv%g-&D%NlYIGgkRvGc3A&8QgDxkHez9M?flQx3A$cKc(&?EFW$uDMSdb(QMw9odi zQA?zO%QwiY&D&*2_|La;le8f+v*;YqftP=UX(~GO>fBxRS{^y4gbh*RyJXj3%v!%! zELfdXKw~e(B^eo_RBX;Th4TrEi|2p2@Hg*5bt%Y7ZIk$P-}GUj)gwz0gIBAGiFNn8 zU4&Na+V|69<~TqZyxqSPaeGkw<_`ynX{4vBxwIX_Ypq#9SqSJ=W^R4opKAeSa3L{m z&lHRtdQy{5Ggy~SFu34>`lJ%Zqqg`)p0E)ulwxhQ-;}L>tXPKb-xTPBQs}1)CSM*$ z)G0-&fr8_TI{4boZwExp&4Rt|u<&mI1_Iy+`yv2(?Zm>&!E#z5*xWy{v=^H#tjEA3 z;?O-=$gFu6kw*5=S@@t1PtJM?AR~Jb<+?`D@ni^f9@rf(6M@{G_~V?Cy-fQf^8)n? zQMliUqyBPjXiOCQo#z#uU#^qooR+z_tHzkiIsIG6rn#gWN}koO1iCdnJ2E?}15?Vb zHv1jpiRE-A-RvipUQ>D1lRSvmj z7W3Og%mVd(!g)KZzdxx03y^c4IMqbhs;z8!D&FY;i56b*oQ6$WJxRAsvOKW!wE>ua zD0mc=bW>_*_Ph03EUervAR2#dSHw8J{!GR_N!df0ZL;vK+=3WRYyZ#GgT>l0+k}~1qIqt zS6WmMZM)!rz7z_m`fK9CHVM8F$z&G%jWzFH!hm|FYpam-1QF?Z)lPOHi8}0f1o9EZ zDHf!)*@a?vnvbdJDr!`&Cqj=g-f;y=uFs7+Jzk$Lqc5IOB(A-BqFIgF5T*Qh4dUC& z&KPT!3?JZJ?!2FGI-p$Yz1pL2ZT@|G!_!$1J@*9lY>pk*)lpl#C(!j;vJ^FY@2K3n z2bIo|a*SE!HzHgWM{6~I(^a*s15DV0tUv$zES9Amg!xeS8?y}$1Z}K#^z*n0>1~He8ZPz~6(W>wyBjvX_I$UA!VL?CFEa)<61QoPZ6E_lJpjc$tmFIQ8ZC{iPDf zO2-9y&-i(=bBR|;{%~gM8=O_tg<9F|DLGA&TZU$Dmt&g50M3#7f)z&Uh;BRwc9Fuz z-1wDw3C{{c-~!Wkhp>&;jVmvmxQJZfG-RppOg1^@pFD4B;*!n~lLSmHhRBGUZW=wL zrq<~HsA?@Fl|25*Z_6NPzj7X+}j+I5Z=nZ2_bWFC7 zTuxY^a9H;EY7yk(wd>FO+r1&Q=A6pE#dPEy^vWSAqgg}SUq@acOCxOw#+d|Qm9XIz zRGFSu)D?W`_1iH$=?m+!uJ;FT$Ox9sW_Mi@heywtUNevsjY|GZ+9y&g$4FCA5uwfk% zf*2q%_Xk{=xlxR0V-lrZ<8c^ny0kflt5f{jx54mj|S>kwam*Tak1b3;( z5uPT_RKvI3-JN1xNUUV?slZ3MO>r6QL6oc6t-jxIO{GxTrzD(yK)QDPpLm+v`7|p} z2gy(VZGC&YNw^Sa`UGiI9uXm!9PVra7Ew3o^o&h~XSGDkY zs;^`*cxA6xHK0$Wic0L>UEZ->|DkX6j1#<+RIHQm=vtR9K&^UG7kBp zohssHdJ&9qvGa3a$c)-8t8?K+cH6&N!v~A?-<*cwix;^Kx->T5?74h9@7rrK!RqW( zo2vJoGt#1rN>*x0wCL^Iy~m|a9o+HOx%%|#GJ$IR^@H56PS~Nk&64x4VbME}59a@h zAqcjHo2qUpv4ru+gtljF5cq0UfGkddYadJBa9qH5nTqNu$*6Eyt0)uW)o4o zI;X)D{>#dI8(%wELz1GF@W7BU?iTh#pd^;0(7A|qgmkyuW5DgLce~io- ziyf8;ON`-an0(auAd<+A^E&OM70amakbMh9ou51y1A4-pKz;ftECew{C|lR<2EG2V zc_YNUU-=dDwpU#60DATW|2Y$&LhL{Md zgU?Q#<3)i(y#qZ1bzpAfA$a(p99$lv#>L?Q)GTy zvV36GhERupL#v>^msU5ZmKGe6Pb0Y50Z_*r_EQ}YYljZ+66G=_SknIB zZ29q((LiBZotu{WaHM14bGk|AaDkw7pRRF+J)Lu6k|cfbwnXs?-X|W_s!|@*zFqbI zKH(l_gt(*O6YGy(ey6N?m_zU{`f$GyG}a%6%QeTyYV_*9CTC!O*p|m9#!SnxQYjCr zx0?Pz4pbv$bbm($)?Vpu@0tzWHsS2>)v#t> z@)vmMMS@d6sl1*mp^|5P{sVa2Ydr|^bT4x;;m;G%!7jv|MnM$?)5Ax-e8U)PJP1|j zw%heI;oCzyygq;2y=EfJqsY192X~vsQkXUXIO-m*UbQ!I#`v`?SW-Wg`74otU4C1v*?+r{tKmsUFh+cJOFn%ei*x1dOd6 zFdTHO)IfMfuFw1>5}qFUpQ-y^y)mXc>I%0whfG<;p=IXi5i)%>S(gUE5DNjBWKBzr z_#Wcq8RL0%$M(|1pAfjAhgbM^y%{*VI1Cxpv0wt>7i8%;SsQ+%*i3Mo@%ohOIdc9n_pG$ewjs26kJ$SwQbo^Sk8@-{F@9Fe^jtAAGY004(QP$Jw zW%MMJ!r8%+p2x)wEYW>%pS&FodEgu=HP#p6`0Pp&o4ydp&i>(Z~^F0082|Xag}ZxCR2>ZQ5t; z>A|WQnDS?znrt%Ye7if=pzl|H131>3+~^IjMyPz5ZIm@Fg=5~D$N*x02W!5TwV`kb z5cs|uy{8RXJNs9M*y;%C*|n%;`^I*cHg&PuVYA{FO+N1V#OU2-1R1gU@ug@Xa?q>b ze*(Sl%OV@%(h7UJ-Bu0-x!o!4QqeLO#F)tNvHiyS;USp!I+M=xg@Z(rv47_0_;K4l zshut-0EL`c=&=BxhuXPiRDTm2%{M?W6#9@tfK~EMaZ8WoQZWLcVe@du#-RsW4+z}g zO%&Y$Psw`fY1m|z2k?BkJbNCMBPap;?iM?k=FSWB*Y9pWRVL?x;LPus(N-8_gAb^2 zM!(Sv0At)38Cm$o>ww`vVSsgov{ zCdYVS8Njokqj9l98H3CsY7CH3qo`^|-M;Kkwb$*2&=wdc*1-MVk+~=0au2!?|GVoi zlb*^0KS?Cd6dOGkZxX~LQMUMnNLwVqKjApVqAuG@J2V4|Fd>bG08(u4#?aCTUfwsl z{TWl42|bHA2xHp6o%d%^K-JUV6R+VEJtB_j^juRPb}G3*dpx1g1>G$4D|Q=s2G}3F z;M%u%O4iu*46HuCLsus<$^K?YHU&?^`|2hfnKp0+1Y(JBc(8|T9J{KMB=@c(b3ro2 zd}F1=?F9afZ~ia~4`SjA>gbccd%Z9QB@zWr+A5TT>sE|}xp#hA#&LC`+{fA1q~Mmx z+3>dUL=K{Nck=f3=8SQ@%l>15p%Xoytnks;MkrQJ`6T31H;fuO#pNAfE-KSZmMP3@ zdV?m2M1M4Ni5x`?cm$`5?d(F2Rn)Mc246oiYT~1vAZvcRa4>RjEnY z8NB%znB~)cz7NJ}j%6vQisQW~_;r>G41dCv^mugKaMV#j1*e|WaXQam%?@nx(d*kR z@V)Bo;iEq2(L+y3>yNCS^$`W~tUB=5o*d2ik0YLVGl&)hCY;~+g$9;+2nOIL&ClSa zTuN#y(f|?&^pdT#|Ez4cA^jTq_=Y?0|BCwVa5kW}eTrH&O080>)LunxYP43(*4|X@ zy@`aP_O8aBMb+LrYL6iH9yKCnjTi~R=Y7B5`2U<|Ki74x^W5h?g}(n)O**8@D0X7% zVv1o98ti#psHl7+4G@z!_b)r-6_a96mysLGA`sTw(Ba-7OH=r)+EA&MQ`L_4tX0x^ zh97RKX4$v-B12RoBIkh@0H=2|>nW{0opXR%ix!QX23G=kLL=*dp`Khm?uTVT%=5qU zl4gELxb+XDu+fPBS<+5c=0N?{hS8o(nA9d9b3JdK`8G~5DcxJQ00$!y=d99=`xY)w zp-=NHMv)Qjt9j(z87hEilFo(355}q1@Z61JoxzK+smK_6!asIS7%bE2S{&+M-m`xqaH!!UdGuQ{MHaAnI2l0j<#hiPzCyfQYWoGe0;pPvFm9 zT-J;f{>>*8e=-gaW$IrStoFN!%a~L;Qa~w)fv1KAARO8J#5#Sm8Z{j z#VBuH3O4+H@pkC~JCMTsw_Q%vgPKQz$H#I*U>;hwTpuL-h7cqpS2-lF(*F7RD~i67 zB&2SfG7B>msr15LAdW>s7Alqm5I~DQGk<7+a$^#JgrrLh9s~7$Xle9d(Mgo*vsD77 z{XEUQAQbTUUiSPIpf#1~#b0Qe-(P5Lc5fhIUulw)PBL~)2q*Ap5kw1*lb26_XnqN}@H)z34&U z?4Hgp4HD1g^PpCA;OR=)fDO?6y6cAq?_jC(#}EdCh`QU>IwX)KN;^qF`M~?}m)5JT zP`Yj~INK=K`7hKcie~x|80v(_XO498{ z%^s9ZU(A!qoHI=zrty!fwL9+QM|?owwFzMRf6~AS2FK|Vrouv>ZbLV&|7K8fNZY)u z_sZaM(dD5>N()A^cp|44v_qzt)7Vu!$_hUiHdi!+Gsi3aMT~4UHg=v|7Nr$)@50{9 z>sQQ{(kob4m;|9pD;r0~k%Nr~Vsm~KY04(B>;tCiYDmM}oAtAst`I3MB8-^1o2*4y zg=}#5@v$pYJIkkeVAjPefCS@EAtJ8tvw2n~bX5N#2M1`#1Ca#)q+jL=(#NqNRit|l zV;QlZ#8SMO5qsok2-sFZGbtrhPJ{>uIw=e`rw!G+gd*hp>*aCy>? zvFOe+_1UcHYR?BD$%7t)pjqZN4t<aVv#X#4^luROO`zvzKdla_cXG4rX=K-zCu|J>K`0jQkZn&>rh- z>q*zkKe)=0ROa|p#N4B4M6USBET+lU%s<_26PUl6swgZeP}E@(*;cNu1~k7XyBjLZ z`HpJ}_F3G%AAjI!fpx$zz!qTGfrip=ZgX!>06=%A<7x8awY>DVcI!75wXO&#Uzb9A zHpP!eJ}**?zDle*Ov-CgAC3N^=C%f#m_;69M2Pse-+jVicE?|p7pHyz$4(J<~(i=wYOGLEU<%oiQ19w`jb~5lv3X_mQZu-QAF5j zyURDVYTRjBr8W-84N##WY~6PKt5@Up{EN%>@?_At1##d*91dmXm79_9O;V`0J-&J- zpK)+*(;)3(T5-M#g*qaET^f{}zKnLz!3M-K{r>y{M~!|6dK$UU0{mKS1)jh089wp^ zYd{j+YOQw%d+yQ?e0FVr=dgLi!3zTw+BkM`_el7$gU;YJ$1KNg&gTayx7TlO%4d!M zt?uykNvryn@^{l4w$F`sbSjz%J*O15cln`|JisON88##nfPU9$(VI2@VJ)y4#^{%M z6js!13fnZP*!`ln;HMR^%EyNq@W#*DCvh1TYB6&#vZSlKwm19H~JQ6?WU;JO# z5kR7Ld^&MB&Ca1I>0t!MCA?GexWe&E#x3p=}c>M%Vwn0Sj)w5+(Zh1v781%P3 z*?dm@r{9L5rIzX@KJW$=;>v3tbcad25&#QagCiBE75^)48;W>{K&Dj_?+f*XXBZ!F zR_V>eQ`v_Q#P&x7ry?n1VXlqKT`eXnzX*Ztign-ZO&3fsm%QACV)MCjOiNwT=Rf@? zyE>F^p~Y9X(2UW~pQF3J5l>#Y@4~0|SZ<;CC`X;(%hUO7L*CnkziIFKcH-Xvw5TOh z`hM3OpEVQYrK*@}CPu^F?*}utYCbXE)Y)67QZjfd%Vop$A`N=Hdo30DIIr^(gHF1G zvq(BMeUX^Ne34-3H7~e>%PNPbHFdm}aWQ!^X#P(YL}d5S-T0_|l4n;p!5Gm?U+7fP z!jB{4W`p$yzKYNU-Cx{?4&c<=Xpg`J$C=E?Pll3-8jyKO;5-)-tLhVDbw&n{oQEfp zof$G!Uf&fSJbY-BLUn8LXFT7c=|_TU%MEA`XW4~ncv(2+JJ8ZUq^W_ev5BP!uL%Av z=w6fluf(qR<`3BpQd!vW)pW8Y%HvP2CAg_7n2!jK^-iTP%`tGDw?^{a6(7LAxz1Rv z3)Vtc$M>Et-r$@L&XwlS{{#* z%?2{~t{;8&ntME~&j1RJ1vVdO;f_^L8v1izz0`GA82%;8E0G;Q!Jbk=Rk*Q9ykP{9 zwvb)l!HhkuHYv7Ct~*nRc}1w4!c$`~1^wOja3=&Y)f{t1-=17-oH(8FS!4=SyXujR zcIH(75Xghz3@T(Jzoi37k;X zrbjpVDeqg4O?>>{{~ew0*i0`}sgF>o_H#p@!M32sD=a(I5fiV}V0=RFX)h@kwli7; z{v~k=mD0CJ@X^Ot(aifPRR8Z|g=rE&)N^HKn|fz(F`b91J~!2` zpdH(30GLb5bz4^RmU)Qg7O?xh9x>9j);4v{eWiVeBtoCjmo1|`ldGQ<_GkYnREV0? zsed4$`tejon3!}p!kRPMC4qh3`uXcD?cG!Wnq;f%-WdXr5n&=$7Hf3o7kgRFmrzTP za(2#kiBiBUD&q6^jT@>qc~U25YJpM&x~wo)d1K&e6S9=jH+B`JWUvQAqO;(17FZBK zcx^2vQ;a>m^3e;)2OBOjk*fw3<-QOGF4nJh-Fe7D@)QHwu-olV&mk**>sJ#6D_-mi z1iuSrns!P{xpKoTmeFUY_g+8@<#l$B09pU8vjyc5#dh9+T8)M76ckFg{#yX@SDV~_ z(eN_~_V>2%zB;6U?-2mK>NM_WQG4enWns>yR_=e-!J)2Xsl~^w{mOUq`;0#r6oN5}O5)y#~?c?S*h_@upl zQSy^#c-Szn|MpDkzu#dd+?fu+QO0NO2y=9U~R?6EJ(#tAM3y9Y}Pi`s}tCNwwa2 zq;(h27Sf=*EPTSC>bujBTN7ViPPcB#Ecj15jlExHvqY+ehUaeG>K1x~-ZQ!Nl=-kn zbP)|!kLykq(9nektRqYaa2aJ4Y+HX~@SiSv>0jRh`im5=!Js~^^?mSxJKTMHjY?v8 zVIE67<#Il@C2JLsypu8oPFN?4$Q&t=oadNY1q>5`q0I*^QX6R zD4HPWPxKb^tRKjS|8J1^U8ka6>G!fSg0%b(KS1{x<2i#afYzM<)w5L?N~eI>r8^bS zwB=5inr;qxZGSPSOpxdJUgs4XN6ekD1eco*;qL{MrcO!6N!%)#{81Sf_ZdZ0`s`&5J~>IzYFU(_%TMg&eCB69q)8it?8MkVAL;BV zxo%KgVZB&PE1{6*vo?tl;p6&BEidXAq~a!gR4^!UgbY4PvXoo}g@|oO-m(Et2NS!F zkxPjdsj0BVqIu_(Px80y`06F@sNN1iwwb6x_Vg18aeQURHJ&uTdSTCpvrO)&fEYq6 z3kicA_FqElr+57>tMvTaU`FZ;BtE3n-*3WeS*+rcB3msBs|q#%!*V=^&TH|tO#lug zbPPScgFy-h)yjm{HnbHr;gvzdYz}3F9Hr66nP~TxkIrmX8^Z`nJ)!Zys*x~i5yyiA zFG+l@ZEzN{bPSEKyJWqYPfKh0%D~e4Nnf9$+>x0>>jaPv0B}yxMjKK9dN#INB!6n$ z#~M#K9cC)sbjALErQN{AgfN~}r#G-nd^BSA!%)DPSJ#9DdyI8_|DY6uymG~$2jpi$ zQ>-1y;*M|Wxt4FZ0VYXZ%}P5%g)eAZQA2i3lr@%Rh9>Gi;cZ+?2|6M>ll z>J}}1wB{2?<>u6mTRIXu8b_BX{J-6><*dVT$eTBT8J{L&!+3C;BD1rvuYuhHF;8{8 zQ)^BjmNlgbTkeqPm6b2sPbI>@NHly0`qJ%m4~6m$k2 zIZ(#DZ)glNu@M>{^c+DeTglVV*KE3 zz`=sp7EzVg64RmB#$|Cuymg-H0)A)kf%y1%`aw98n5=6hg=p&P? z9q7RG#bI#wICqbtjv;#y(GF+nK1a}HbB-7tdu9GF$2Pgu_4T~DPkel(q8XK3CJq(1 zAC&RiyOk-5UhcMTr#5%4ji@2Unq*H7_EX#ugj1x}^sm_IViJ>6VtXUE;R+luu`SxS zid2!9y_hO<`fuf*arD<-?Ha_lOOseuPzM8$bU4?A*sC9cZMMek1n--73oL!8@)pjyO^GmWJ17DxbFwwZ?>PB5AxD)L!t0M6y6OJ=5Dsw^k3~)39Ki*1MN7*Gu^uS zcn2ap+}(4ZHAsif2>)KEH>p06lgOv6=0G_2N5}_XW_dM9l$k0lJwQQXB6!9yMal|@ zbXo@n?{+f2J1Zi(fb&EZvlPlPkN^fu8K=Oj}FISvK!kkR6w62xmiS0Lm;_ZMs)w*hs^uk@r zi!K5FkcuzOzxd}}b#6y?Y{2IK?54LDxNG%A1Hq!38nzu+3^^G z<9OWrZhVDE;@Z)L7>Oi}<6d6_9`57qhu@MG<&LdMm}#<#QEi@u&Rwx*`77q-=GEcA z5F^+3wRv~92WIm^XWqu4T34W-bOy5BHI>DC-7&le9XJIc-9a6loj73@iXV;nNy(qJ z_}?B;Rr^s#lI0NVq)>6Gt&Yoi$uQ7-F1?^sOvJTP^G;16O92yqCD%ml3T*6hMT^cD zRhluHrmM&l%HA}1HO(I6d}*G`{Da!T;rmwPC#YHqvN=t^<_i>b>q;Ga&Zq?e7X9hi z^?Kf3tyT`bv}nw;|Liab90mNtt3>fU=4x!t!~U%^>pt;8zx2nV9QVoSvRJMyNuDV4 zv5Vj@Ls|1FBE98xkWy@yx@M=zr+cT&=69&P=^Oe9ecMjl?YCGkkH3tAX6!->L<26a z-Kg!x>&h_wj#OmYG;#eU#N4-U&PK*y#A8;EmkrSyt!&*P^jcaJE-URVhK(k7!I#}7 zc=cQy|EzTJo#&*)%~(VeI)E)Fhz_~56ulIyB(s=2bG$Zhg}O%hcQ48ZpVFc$ty_g! z4u*znqi}Gr_df07jntKq-7VeVMQ z)(4M;)lp~vVqfa%Obd9n-rQ>an>tT`U`AzYOGZSDWm!PYkg=p9;0|orKEhTn=sgt0 zhEQj=P+%$H{P0mS#W^G^8rz;o_v)Z*!`XJw>E^K0rOCb_mN4MOJoyKdyMC7uIc9qs zcSVNQ;d+48Hzg}l)fE*^wjps=YV?!StX^Q@=F8I-e<4F+{+B)Oc60S=0(*9F(Hart!5pnRV_aE_nI zmVuGYkmwOX`_Pu(_Iy=PLlpa;@!Cpv8tCA_a?yVJ`_lSP840FezVboo0}!P7RvJ_R z%{uS@n$mvYl=vgv5%DPIfOfiRRw~*9b@9XND9E9zK|!HOJx+0-$jkGj_(bsap={g} zQgi#dC#hM3c>CmNhb(dN^QiHh$UML0pU2DRz+b5=D+ zsWOWdnM5vx4IeU1IiE;bL5t6G0A|xb+X}sS=8pMK%zk{f4%bmba?HMRt}ek7-rEj< z#fvb0@~Yr8mUaE@v77VUg8ua)b|$=-eH(N0^zd8^ZAeN-cw2_QKw=y(qF13Q6{n|f z|M!)oB>&Kr5_DKHr=^+*rB_gt7sZaMNyJ}&uajMfm8{TL@{0JBCfq;$D#C+yezLb; zd|T_|=f&VkKRy^BFvXaF=-a-5{Z`eS_5AaebP?Q=PG&*LD`(%8Pp%pH^}ee7-`+;_ zFL-A9o*_P$zCSMt-D2j$k$5#MG<@eFcOUf4^oNC|Q?dlH2houFlWYcmg=05|%bh7? zeM~}MtKI5_4Fr&Wj2)r15)|}*x_nSwq*UyI@@N`xST2oVpT5N!XHi{}D^t3LW z)QWYzln?}cv`F-@tpJ-bx;2s|w(^WsB^_*bQKh+#fV_AwFOu0j+L zhwf}0{96B>DmmoSin7%d_O_O{J?}3_-K{!xpZ7NQ_1O(piGa>BCsb~N8fz(%;B5`S z><96Y71j{(#eq3vk|K+edR73!{2M5dH}c1Qy|cIIhJzvK@RXPKN|HlJ7Jc}YZ)x@R z=6GiB+z>kK;_-@eC`_D*ELPO!BWtwUb{4TlSlBi^{-ZU3lRqhQOT4Oj1Jq$=W>0VM z+{dD6A_66!;&N;G?v>?NJnBa*+$P)Xf=(NM%N(uPBV1I>u+xMQdzMejPXd3a z9q)SU?37-g=>@v+(O*b`k6cy3-Gpik&WnP&pu)H1!R2pc?@srJhOS1qYmqM9$E}w4 z(b&5mLotm9<t93*u}%_?&I@<({Y~xI@y}YYbBk;1;BMyD z;^O|%)9HzryP2v{H^`S(=iy}m#Zv?v-Rx5NHb-kYv%5T}@YGaUER3yRC;>xehpD!es1gMDY)rLAZ4`DY_hw!C7jR>u(TKM-eB8GtSm3a zstZT$5maSzy-rWzwtu?^K)ymZW95bGe{|MtH1A7e^2Jj zh&aEAV%iw0dSO6u2A+JGRA_OB+bc^SPqbZ!3Txk_Z=2>rQN z=Vock1nN#SB$^R)M-Sle9ulB-9$_v3b(duYR-=9@OfkQ`+}vu!_ReUIg6erUr9` z7^=Hgn6q0LrwQ1a{$~BSfVntOrqCTWDg;%v-waLrPIGb1|1^KhHvi0K29+EG$LGB| zUTFD@uEmy}4Gw1v9*w+?J$S?KW>^EXx)N2+TC zhONu}Nda!+B~dT04W+#&CLTBJcxA6 zPcr?5?VaFqQp3@hM6^I-40PiJ{kS5$gGlOXz$JK?u_l-{sk z^&S$X))sE=9Q3;%q{FW@Czd1#hf#5VtC(ppQgOw7E`vkrTc^}|fQ-3!v_JhmiKM|HrA2=Bl&?)2e)`;lG^#ZViDV4_R$p6~Js? ztK4U6+^#q|xg*yn)6VP}v(xi9#8;AAr`&=Zn~=W#0?9ANmZ)LzXh=a~C+wtPXUDyM z6h@*TXZ5@<{^5>Hy!mSll$Etg)A9XMn_4$PVj>{!fBQm>(Uu>GWFg-A1U3%q- zIW{nU5#n6K@#^b}C`pGruWVi~g0^OSuGJqe-QckH;(U>ljsE?j&C@rLrKlj?dw~zF zSm$QbZSRUF!86E4BvL`}S%M4Jt+2-qE~L|xS~P;Wva@JQTSLutv&NZLtoo~^Vt0tb zmjFzeDM|3wz>BmVNP=3eCmeQOYTx*7sZ1kyw%Bu;z85%+ zq@9l@iwHik5aU-k`WKtEIk@&K@n2U<)!}T5MvHm-%|$QF;vQ0)G6^N?rpU-HIrwZR z;|I7qQ_QvKy}ZrK1%N&Zke^v|DL2$UYEX<&c;LkykuJR<52H7suV3J^j*J6JKh0PN z#Oy6qY&&6Fk5bo94sA$KmQvJsD9MwS`}qFif2tL-SS$0dpI?Zc(v;*oAHxCD4|MA- z4F(8{p5fONvZqT8@lF=nGL{2+4*D_s$B(k5}$UmeZ7|j zD(=(@Hiu`Ke7^e^)z#Ito@z{&pknX+4Hje$XR;()V40J6`k3|ScoU!Pabun5@9%mP zmE0H)8ujqF3@j`{ssH>D@QaMH5^8TCZ^LDO{!!%PNEn6MW7YyC+i#)^Ow8An7w4hu zJ@(nP%+vtDo!CBc0r?3jw%d0#ygUU24b7gQ#AL4HJ^wT?jFCKsgZ06I)s3?0qQi$N zB1!(9M3$G;5+Nl%L^iTl=&#ok5~E5*pOeBWrLW$koe8@$Zw6)W)1O4YY46?P5(SAV zQT%^;4ds0^Zq*?DWKH2F&`MIl^ zWEn%ensMHAjJ3`FI1qZl*{@K`N&MXJDJ!0e+qa*e+GM{4^Tk)bR+MV8-stG&VK7`i zKAqZPTO9O+%>d^;IPwo^(&- z+FY-X4}F7=lL%`%MHaXyLv>oz)~+?>bxYyv?uV!4Q$xcnTb0^<-wehR<%%U;Jo>Og9FXpA z7+m9CzO^|~+=lCrvnjn1kK-e#&g&3sd&NfXGTJ0kul{Ll{gzl81UqJ8_%IE*41!RmC`9Gbpt%HjA}7%@P?8(&foUCm1E*2&oP zA?!^}75N2RqeGh;addDgdKQg0I&z5<894GRqif|!!3NMzWJqa_F-WrD_LYmrp1Hn| z-7Lagf`8mNvVumy?6;R;ff`k9|FlT-ilx{F(5Q|&)E(*xCmJ>xaZjpw`2yF}9d;*_1R z_t7&i=K$3fV-{5>8-EF-Ja#@rS&T{rkI-8f{%WI`b)?cK3Er*wIuc1Bfos##&3)2p zP)wC7<6gKp`E7wy8J?h-et+SU-WxMo1qIc0l;u17=TaMHv%A&z!NcLz_iUq}^ALcRQGp zO3#doE5|#DE|A17N&RrT%=+<_Q}UAjR}>vMemq*pZZSq4keZc7wkj?Tyw0KDeUqAX zGZq}z9c5m3xA==aFv2W4<~sN*{{4?ULGuufMXW;sxyI+iSm?i7hO@%9UYV(+`Q>Nos%vF8g!Usd2P z;4~-_8`!v6@(tpz_4Q(RM26{pkU|)UyNr=ihw-ukPHw<UpU+AXw!RaEXpRZ`!! zYg8dc?5IoMJQ2hB>hz-+?AEJm77QYbCtHtF_p0^ms1x@`UMtAF;}i{5AxiVl9DDpj zl)*5)Ng<4^TDD4i$KlbhQ-E&f_bUF+KzD6OX^sBayL(UNNV{|$loE2{yD|2UlLV?J z@Ig(y`w&7yeCv-`?uUV^&4RXrHsy&k@i}adNm;XgZ!a@xnvjG)yI_LjRiUqV%gYIh zTK1D&S;x6J%jL!y86wNhlMbcxK=q;CDA?OTEGBAUdVZ$JYB=ElyA%2HUEC_MuhHw9 zfP)~1CR0x8cHDC6+A8>NSYxQ2z$vA2UJn>pzZdq@C^#Xoh zdqe|=^fm{HmPOP#EjbbH25nT$CZP%K7azkF(mG$3cnFnvV!sc|V%0fVJ$l8KpsRTu zO8L$dH*_-Z+K;9`{p&$Rca2+turcwk=8~cyK0rNk55^Im*gM#q=U-^i{<0)$3uHRn zH_J=aK6A*?VLE!3Hi&0;r$KN%3v1#-jxKH%pl+cXKmYXX5gm8@@y1#xCav0t9od(z z48bdZip}mIsrXig{8+&@W$YEwRGTr);Lw|2E0DvqPPPlK%Q*y-eRpGMtZQa*dHiOB zm&!{b3*PxxlCIhz1he8Qe_ituN*=VlqosmzZgl~c62oxde$5Fm7!q248t=D%7jc(T&EAIMN0uPq5-R!nvG8HJu)x# z2l7Bbq!k*ScO@_{>}1p$JUt%!O}$q309mlnN$TVTn`5E)<0cDkchxB5N9ij>^1C4R z#OSfF27Mj!AhRy0lnNE`7ddO(RS@~@s9$AV72Rat8_}SIGlyS`bO`b4OLVX-@+it2;l!x9Kc))(Q=DJL~4JFw^ z(QdVI!ny}MfWXZX+W7j09)ZfAZ3qAKqN*1(7zzgC2SM1%t1q&GJt^ZKz5~NjeW$5Z JrC|B>e*nH7H{}2T literal 0 HcmV?d00001 diff --git a/docs/docs/tutorial-extras/manage-docs-versions.md b/docs/docs/tutorial-extras/manage-docs-versions.md new file mode 100644 index 0000000000..ccda0b9076 --- /dev/null +++ b/docs/docs/tutorial-extras/manage-docs-versions.md @@ -0,0 +1,55 @@ +--- +sidebar_position: 1 +--- + +# Manage Docs Versions + +Docusaurus can manage multiple versions of your docs. + +## Create a docs version + +Release a version 1.0 of your project: + +```bash +npm run docusaurus docs:version 1.0 +``` + +The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created. + +Your docs now have 2 versions: + +- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs +- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs** + +## Add a Version Dropdown + +To navigate seamlessly across versions, add a version dropdown. + +Modify the `docusaurus.config.js` file: + +```js title="docusaurus.config.js" +export default { + themeConfig: { + navbar: { + items: [ + // highlight-start + { + type: 'docsVersionDropdown', + }, + // highlight-end + ], + }, + }, +}; +``` + +The docs version dropdown appears in your navbar: + +![Docs Version Dropdown](./img/docsVersionDropdown.png) + +## Update an existing version + +It is possible to edit versioned docs in their respective folder: + +- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello` +- `docs/hello.md` updates `http://localhost:3000/docs/next/hello` diff --git a/docs/docs/tutorial-extras/translate-your-site.md b/docs/docs/tutorial-extras/translate-your-site.md new file mode 100644 index 0000000000..b5a644abdf --- /dev/null +++ b/docs/docs/tutorial-extras/translate-your-site.md @@ -0,0 +1,88 @@ +--- +sidebar_position: 2 +--- + +# Translate your site + +Let's translate `docs/intro.md` to French. + +## Configure i18n + +Modify `docusaurus.config.js` to add support for the `fr` locale: + +```js title="docusaurus.config.js" +export default { + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + }, +}; +``` + +## Translate a doc + +Copy the `docs/intro.md` file to the `i18n/fr` folder: + +```bash +mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/ + +cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md +``` + +Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French. + +## Start your localized site + +Start your site on the French locale: + +```bash +npm run start -- --locale fr +``` + +Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated. + +:::caution + +In development, you can only use one locale at a time. + +::: + +## Add a Locale Dropdown + +To navigate seamlessly across languages, add a locale dropdown. + +Modify the `docusaurus.config.js` file: + +```js title="docusaurus.config.js" +export default { + themeConfig: { + navbar: { + items: [ + // highlight-start + { + type: 'localeDropdown', + }, + // highlight-end + ], + }, + }, +}; +``` + +The locale dropdown now appears in your navbar: + +![Locale Dropdown](./img/localeDropdown.png) + +## Build your localized site + +Build your site for a specific locale: + +```bash +npm run build -- --locale fr +``` + +Or build your site to include all the locales at once: + +```bash +npm run build +``` diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts new file mode 100644 index 0000000000..fe6e79534b --- /dev/null +++ b/docs/docusaurus.config.ts @@ -0,0 +1,119 @@ +import { themes as prismThemes } from 'prism-react-renderer'; +import type { Config } from '@docusaurus/types'; +import type * as Preset from '@docusaurus/preset-classic'; + +// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) + +const config: Config = { + title: 'Talawa-Admin Documentation', + tagline: 'Start your open source journey here', + favicon: 'img/favicon.ico', + + url: 'https://docs-admin.talawa.io', + baseUrl: '/', + deploymentBranch: 'gh-pages', + + organizationName: 'PalisadoesFoundation', // GitHub org + projectName: 'talawa-admin', // repo name + + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', + + // Even if you don't use internationalization, you can use this field to set + // useful metadata like html lang. For example, if your site is Chinese, you + // may want to replace "en" with "zh-Hans". + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, + + presets: [ + [ + 'classic', + { + docs: { + sidebarPath: './sidebars.ts', + // Please change this to your repo. + // Remove this to remove the "edit this page" links. + editUrl: + 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', + }, + theme: { + customCss: './src/css/custom.css', + }, + } satisfies Preset.Options, + ], + ], + + themeConfig: { + // Replace with your project's social card + image: 'img/docusaurus-social-card.jpg', + navbar: { + title: 'My Site', + logo: { + alt: 'My Site Logo', + src: 'img/logo.svg', + }, + items: [ + { + type: 'docSidebar', + sidebarId: 'tutorialSidebar', + position: 'left', + label: 'Tutorial', + }, + { + href: 'https://github.com/facebook/docusaurus', + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Docs', + items: [ + { + label: 'Tutorial', + to: '/docs/intro', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Stack Overflow', + href: 'https://stackoverflow.com/questions/tagged/docusaurus', + }, + { + label: 'Discord', + href: 'https://discordapp.com/invite/docusaurus', + }, + { + label: 'X', + href: 'https://x.com/docusaurus', + }, + ], + }, + { + title: 'More', + items: [ + { + label: 'GitHub', + href: 'https://github.com/facebook/docusaurus', + }, + ], + }, + ], + copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`, + }, + prism: { + theme: prismThemes.github, + darkTheme: prismThemes.dracula, + }, + } satisfies Preset.ThemeConfig, +}; + +export default config; diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000000..65e9e455b5 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,48 @@ +{ + "name": "docs", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids", + "typecheck": "tsc" + }, + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/preset-classic": "3.6.3", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "docusaurus": "^1.14.7", + "prism-react-renderer": "^2.3.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "3.6.3", + "@docusaurus/tsconfig": "3.6.3", + "@docusaurus/types": "3.6.3", + "typescript": "~5.6.2" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 3 chrome version", + "last 3 firefox version", + "last 5 safari version" + ] + }, + "engines": { + "node": ">=18.0" + } +} diff --git a/docs/sidebars.ts b/docs/sidebars.ts new file mode 100644 index 0000000000..68d3ae97bc --- /dev/null +++ b/docs/sidebars.ts @@ -0,0 +1,33 @@ +import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; + +// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) + +/** + * Creating a sidebar enables you to: + - create an ordered group of docs + - render a sidebar for each doc of that group + - provide next/previous navigation + + The sidebars can be generated from the filesystem, or explicitly defined here. + + Create as many sidebars as you want. + */ +const sidebars: SidebarsConfig = { + // By default, Docusaurus generates a sidebar from the docs folder structure + tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], + + // But you can create a sidebar manually + /* + tutorialSidebar: [ + 'intro', + 'hello', + { + type: 'category', + label: 'Tutorial', + items: ['tutorial-basics/create-a-document'], + }, + ], + */ +}; + +export default sidebars; diff --git a/docs/src/components/HomepageFeatures/index.tsx b/docs/src/components/HomepageFeatures/index.tsx new file mode 100644 index 0000000000..7e72738d82 --- /dev/null +++ b/docs/src/components/HomepageFeatures/index.tsx @@ -0,0 +1,70 @@ +import clsx from 'clsx'; +import Heading from '@theme/Heading'; +import styles from './styles.module.css'; + +type FeatureItem = { + title: string; + Svg: React.ComponentType>; + description: JSX.Element; +}; + +const FeatureList: FeatureItem[] = [ + { + title: 'Easy to Use', + Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, + description: ( + <> + Docusaurus was designed from the ground up to be easily installed and + used to get your website up and running quickly. + + ), + }, + { + title: 'Focus on What Matters', + Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, + description: ( + <> + Docusaurus lets you focus on your docs, and we'll do the chores. Go + ahead and move your docs into the docs directory. + + ), + }, + { + title: 'Powered by React', + Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, + description: ( + <> + Extend or customize your website layout by reusing React. Docusaurus can + be extended while reusing the same header and footer. + + ), + }, +]; + +function Feature({ title, Svg, description }: FeatureItem) { + return ( +
+
+ +
+
+ {title} +

{description}

+
+
+ ); +} + +export default function HomepageFeatures(): JSX.Element { + return ( +
+
+
+ {FeatureList.map((props, idx) => ( + + ))} +
+
+
+ ); +} diff --git a/docs/src/components/HomepageFeatures/styles.module.css b/docs/src/components/HomepageFeatures/styles.module.css new file mode 100644 index 0000000000..b248eb2e5d --- /dev/null +++ b/docs/src/components/HomepageFeatures/styles.module.css @@ -0,0 +1,11 @@ +.features { + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; +} + +.featureSvg { + height: 200px; + width: 200px; +} diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css new file mode 100644 index 0000000000..2bc6a4cfde --- /dev/null +++ b/docs/src/css/custom.css @@ -0,0 +1,30 @@ +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ +:root { + --ifm-color-primary: #2e8555; + --ifm-color-primary-dark: #29784c; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205d3b; + --ifm-color-primary-light: #33925d; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-lightest: #3cad6e; + --ifm-code-font-size: 95%; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); +} + +/* For readability concerns, you should choose a lighter palette in dark mode. */ +[data-theme='dark'] { + --ifm-color-primary: #25c2a0; + --ifm-color-primary-dark: #21af90; + --ifm-color-primary-darker: #1fa588; + --ifm-color-primary-darkest: #1a8870; + --ifm-color-primary-light: #29d5b0; + --ifm-color-primary-lighter: #32d8b4; + --ifm-color-primary-lightest: #4fddbf; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); +} diff --git a/docs/src/pages/index.module.css b/docs/src/pages/index.module.css new file mode 100644 index 0000000000..9f71a5da77 --- /dev/null +++ b/docs/src/pages/index.module.css @@ -0,0 +1,23 @@ +/** + * CSS files with the .module.css suffix will be treated as CSS modules + * and scoped locally. + */ + +.heroBanner { + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; +} + +@media screen and (max-width: 996px) { + .heroBanner { + padding: 2rem; + } +} + +.buttons { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx new file mode 100644 index 0000000000..270b7f9af8 --- /dev/null +++ b/docs/src/pages/index.tsx @@ -0,0 +1,45 @@ +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Layout from '@theme/Layout'; +import HomepageFeatures from '@site/src/components/HomepageFeatures'; +import Heading from '@theme/Heading'; + +import styles from './index.module.css'; + +function HomepageHeader() { + const { siteConfig } = useDocusaurusContext(); + return ( +
+
+ + {siteConfig.title} + +

{siteConfig.tagline}

+
+ + Docusaurus Tutorial - 5min ⏱️ + +
+
+
+ ); +} + +export default function Home(): JSX.Element { + const { siteConfig } = useDocusaurusContext(); + return ( + + +
+ +
+
+ ); +} diff --git a/docs/src/pages/markdown-page.md b/docs/src/pages/markdown-page.md new file mode 100644 index 0000000000..9756c5b668 --- /dev/null +++ b/docs/src/pages/markdown-page.md @@ -0,0 +1,7 @@ +--- +title: Markdown page example +--- + +# Markdown page example + +You don't need React to write simple standalone pages. diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/static/CNAME b/docs/static/CNAME new file mode 100644 index 0000000000..2594cfef31 --- /dev/null +++ b/docs/static/CNAME @@ -0,0 +1 @@ +docs-admin.talawa.io diff --git a/docs/static/img/docusaurus-social-card.jpg b/docs/static/img/docusaurus-social-card.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ffcb448210e1a456cb3588ae8b396a597501f187 GIT binary patch literal 55746 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 fgzs%Vr*-jYG(YMS<ttdtee# literal 0 HcmV?d00001 diff --git a/docs/static/img/docusaurus.png b/docs/static/img/docusaurus.png new file mode 100644 index 0000000000000000000000000000000000000000..f458149e3c8f53335f28fbc162ae67f55575c881 GIT binary patch literal 5142 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 F{0H{b<4^zq literal 0 HcmV?d00001 diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c01d54bcd39a5f853428f3cd5aa0f383d963c484 GIT binary patch literal 3626 zcmb`Je@s(X6vrR`EK3%b%orErlDW({vnABqA zcfaS{d+xbU5JKp0*;0YOg+;Fl!eT)XRuapIwFLL`=imZCSon$`se`_<%@MB=M~KG+ z=EW^FL`w|Bo>*ktlaS^(fut!95`iG5u=SZ8nfDHO#GaTlH1-XG^;vsjUb^gWTVz0+ z^=WR1wv9-2oeR=_;fL0H7rNWqAzGtO(D;`~cX(RcN0w2v24Y8)6t`cS^_ghs`_ho? z{0ka~1Dgo8TfAP$r*ua?>$_V+kZ!-(TvEJ7O2f;Y#tezt$&R4 zLI}=-y@Z!grf*h3>}DUL{km4R>ya_I5Ag#{h_&?+HpKS!;$x3LC#CqUQ8&nM?X))Q zXAy2?`YL4FbC5CgJu(M&Q|>1st8XXLZ|5MgwgjP$m_2Vt0(J z&Gu7bOlkbGzGm2sh?X`){7w69Y$1#@P@7DF{ZE=4%T0NDS)iH`tiPSKpDNW)zmtn( zw;4$f>k)4$LBc>eBAaTZeCM2(iD+sHlj!qd z2GjRJ>f_Qes(+mnzdA^NH?^NB(^o-%Gmg$c8MNMq&`vm@9Ut;*&$xSD)PKH{wBCEC z4P9%NQ;n2s59ffMn8*5)5AAg4-93gBXBDX`A7S& zH-|%S3Wd%T79fk-e&l`{!?lve8_epXhE{d3Hn$Cg!t=-4D(t$cK~7f&4s?t7wr3ZP z*!SRQ-+tr|e1|hbc__J`k3S!rMy<0PHy&R`v#aJv?`Y?2{avK5sQz%=Us()jcNuZV z*$>auD4cEw>;t`+m>h?f?%VFJZj8D|Y1e_SjxG%J4{-AkFtT2+ZZS5UScS~%;dp!V>)7zi`w(xwSd*FS;Lml=f6hn#jq)2is4nkp+aTrV?)F6N z>DY#SU0IZ;*?Hu%tSj4edd~kYNHMFvS&5}#3-M;mBCOCZL3&;2obdG?qZ>rD|zC|Lu|sny76pn2xl|6sk~Hs{X9{8iBW zwiwgQt+@hi`FYMEhX2 \ No newline at end of file diff --git a/docs/static/img/markdown/misc/logo.png b/docs/static/img/markdown/misc/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..985fcfb1a3bce8c3f019e2691ae56090f23f441c GIT binary patch literal 14141 zcmeHtWmKF^wl2XP(h%GsK!UqVTF(>RTLa0?Jfa7lmwfskOq-66QUC%C)o?d1Dr z=9@F;th45>d;d-M>VDs<+WV=!pW0RZ*6xZ_Q<23&CqajUgTs=SlhS})`+j{;kzwC! zkmUl{rQJtM&rJj5N#*3?XlY{)p>p$ff>1%cY%Jm6yyhI#fPfAv)W+K=QQ(tjS&{&u zamur6Y3j5Y4{}0_jAv-~_dem>aCGR2Oes1FT57q-IcTyWN-A;ms(UL22D?82;)(@N z_j|eyv(DX!^>7DI248y;R%=_YJ={SM37p)xPdZ}KNvTucEr>UckJT=)fN3v8N$TX7?;gjd}vTSTNP- z21%JD9-mOQt?I{*Zu7~uE`41TT_ElqoU@0MhFcuqF$LeC)5~e$t>-0$=OJ9SHaLAU z1(AD^w_-anm8~cWp%j(+4JrV&e}Hp??ghf~vHM#%)58_v{>OzZupWy6ChF@>dCOGZ zW8~w3d6`B0v_NYPuV6MYe>AqQ#lDf%#~lAgzB0Pd2j41&2vw;AOsyuriq@ zPsT}DWB1EVmLY@UHo?m^Hq2+|Nsy9pGD(tXy$fV8&?i$P%He&0mlxdQ7YtIS_fv|N5F#4%LW!)ddf$oaTHG<{UGz&>_h z0Sg*2QFLK1K^TAo#0^B{ z&C1Rq?PcS^Nh69*CG28hDX1YO^CtwXBtm2D=H?{G#^&ki$?D0)>gZy{#vvdez{bwW z#>vS7lVEZ6hPr{gSfH-7zaaj=kb<~^U2L4(Y#gCfzc4}Oj_z(EG&Hbws=wLi;H0el zcX+7lpDe)mVDkbwv2n1nvpG1h{Z+%&P1*wn@~1)nt%j=>Y>u*NKwKT&UBD1&4+zwa z_OB2Y;J@oTxx3i^cEk3hm$LH8YJb=GMS+!#gVS#<7}@_M>1Jd3 z53>HrwqGs3-TA8_F!jIV{*(0IeE%&BlTua|lyU^S|1wWrN`&TD|AH2dU>gg;KW=#~ zxFG@@94wZW?0hWTd;$;_a{-6|3l|qZpE(B)ABdA3@)sz1sH+<{bG}YkBggAfRmG( zj~n*I&;Ji09f*r7EE0cVa<{o&28VG)Em10xpnD^6hmzcpaK2uiv@KyHpMT8@tP zA~e5DqWY!z_hx0-bg}@sfuulg5Ev*sC$}I6ryvK976*qQ54#{Y9}7FbAp2kB9W87u zz5h4qUz3MQ_>Uo%vvGy>@BO>#j~S&2asH$9N73Hq_f(>y`aLNGLEt}Ja0PilEPmSw z!}_BMYz=~1L13%LpW*tqdYk_y6u|7fARbF|UKR*1*c=uLAPyFDOMWnm1*ZikKOcmf zkC*3<@%uZvtD~ixC&&c?w1V*n;|dm@zqz7f{5?=F{@E5!YsjxrfDy(53$=eGjO|Z{ z*?z4VfAd(F?O!+%{w?sAMFyt#M;&bOf~|#Ye_ss$z`rH@pXmAzUH=vX|CaE7qU-+|UFiSVPC=lsryx(*X30RRV+OX-LN-^F zm4bWx_5RrOIRRFK<|L=@3I~UQ_v`ZnF8u=$tP;gdURfGt3lj&AlkGIFo)`{}3Qk@M zsO2@cpW*4IeVKG9wz*(3(GZGWFhVz4*2+8h&dfea8Lis6Mz?&4F73pm@BK;sn(~Py zr=4Z7JbfBILp2^l?E5zy$VwdfwBiz|=Pq&L1vwt>c}$BgOO){PmtT#waqmR!Y*^d` z1TQ}<&o1B3`#xR>$of27Z$3kWZ~NAxQ>gtWSeza6`Sa&Ja1zY$Z6h>Kd_rkDX>c)F z065vGWvGZD(H4Pfp?ZkHK{QX)aH_K>ZJ%>-X}(GQ5kyN%3mzGUaB#!7^`K^>B1$mA z2jNDEQ-#vt{?}rt8X`P3evmk-G!?GIQ`El}|EuUffR4;^2_&g%`J96GC5BbU{he?u zi+|$QqhM~Ey~dY#DjZnZi~w1FlHzx@Z*URpLqXSs_D~_c_1vq~T8O_~?%)S=U@``c zEKHDbNF90z_@!bkljd3w5~hY2?2AQ>-^R@&t&b{w1jjk2rhxa$@Yf@XR79BaTnha> z7}=;4;#v<0Ow4~+ekA@eu!yqj;^E`euclf=B#YIp0iAf5GtK-U2CbrqV5^S;82B|r z3Gz~IILlb1x(k(_%PV^4&Ltj|KKG@W#avlby|{rU6yM+0m58tIe{UTdZmmVmX+a56 z1Wu{6j3{yjPMi+M9V1k!psxFO+xBm6Cp@A=gO6ldm$mL9&m)&N2v7Min-UJ*XSR5f zMU=0a`C#Tez{69(CqdKSyhe=ohY{0zhY(bJdh?E^t|Kpb?5F2SId<7;u39y5lt+6p zTibdny@7g+Irv#lnDh^D5e3J_ zWb7K~IpyiQ3$O^6(lM*`4Zw)i`AMi+KFZ6gr{k#eh}M|2dluYGv#i!)=n$c7Cob;2 zmP-jv`Nh-K-Rgp01QGS?0nS;s__2~xvX5-6`B4viEyCm6MbMg5H%l&!wImqF1^tx%bhittR9t-#$1 z6wDx{OrN95)ERQU`F<%QtIyT#NZ*|z|#y}(9>kHJFQxu#-MXinaHUY9lYRnznkRx&k(uQK)x)6+W-F`4ha% z48AKC33hfz|Li7)A+7%5F8gTNdwnI~#Mzj7%Ga747++~EL*$)do9Mu}asKX#df*hBr9`SZ zFksi1=hod0F^?%jJJ6cl4;;NN?%vMozL&i<#_ZLKf70=dRiRzj1UxYuLHvk(iVeJ5 z4yJZ~bz9+w_Dogsr>5E?8o!X#miuzfOx>JUdP~oZZ*|t4>{63=T7z@l^N9jkMje|q zwDdh5G~8qIR9{@cBf=d)EEk;+%Ct#vyMx;0&(1K5@{-N&&kmCL3oeu+UsFQLr|cg# zR&ZvVSF)}Ocqwo1{rh&DP`!~fE+qO$algG14q|c3jOZ)9M9uf7tgY|f7O_|!bdEYq z$?_2BTh~M>u-#!YOouo3_Bzi2PSaWRzR5x+s_vUrcJ4=N_M}cRMf!BAaN2W)e7;)lnSsrnqI%**Clnk;jI8^^nSIe{I>% z>+^F{R0tKJ=mT-UWy0mQ%cZA;?Nrm1-@`>%H!_C|)^R@LR>3iK)^`409A^Xjfz&PsE{>Uskv3 z9YUIW9zVlR0z>`yk=PM>QHTfC7hnq!U@1^Kg~}YK2VBfpC1E#}x)>q1!2p+`!gr;n zAX#q#kkECuzMsD{AGH%+qm2A6%U1m3`c0vA^)uS^{q$TvbS=n!ZKz_J4riRmy?LZMSa-+a^u*WFVLh@3 z3GvHhE5jV@?{mxD?AORhb*qZaNlJq)im+@+3yclA4OP4n17_{JhdJmDNutZ|K z$2s=gUT!&WlaOBiA#EJ_u7SYoJM9~zOx3R&KhB4}cIVWYjbT*f=j^|Hd}rMHp8D-k zcSN&f?Oe?vCQ-hS9(?5qzRE=u>D&cyF+g%Y?6_)%bYn3kd%D*6|w8CB5nquv;D3*xWgpr3!ZI z2s80H!*jrhf6}jNVZC9&x_njVJ7@TqF|iyHp948TEbm%8*Bd!{0+H}YK-9{C{(S8< zc~1sL(xb3{oE$x>54i>#aI7Ve+T{-RXcciyRD-NqNB+iFCQjSHzh0WqlJbC%kHlG5=P;yZ6!%9ae?2KA*8tDO&qYQJaaNf=A3n{z21$Hu zE7IKg5+1|nkwRCWp~K3TxmkQN%nUz69*rr&_`sJ6g^u{lEZ^7iOj9V`bY-WLEo;ZcLcN0Sp*R=h`QOaV zt|yi>y2}P?_`TQQhW%C>8f$HOal>MP2j~;l?8M(|Vpp7aT^ldB9SyOa%ko^b;3GK) z&ASI2_M-IVE1_=P*_j3I08gPxKWulguxdp-d~<2>BQD%)0Ff0lWsegKuQS zwBv@tl>E}*UE)IFx)%x}ww(xyY9^@$BN66RFK02{GjsQ8*P;zXF%~g#;O!R4^~&95 zxz&^h#alUA1HNh(>(Ce26^Q#GMAca@;>ArZCFVtIFC@1ZKcoWH2LpdB;$FiOt%M2Y zYcP6_q)F;kLrF0>r!*2hgG?XST6zkh$20lG4sIgSqmA)zPGNpJgmQ5=pzOVx$kH;tdBo+LYpe|9jp zd^eIpbs{~|yV5^8B79Q7DN>@d1LpcaNwz&dBy{G_*w* zO(Rz)chc66$Ty7l)?Ku6Zxx46MboB+Gkv~HLit5wdSwXtXf*1Sl2UYy!Bhf8b=286 zZ7^mX^D1E^K02sWa-#oZbnPv)p@FO<0B6pnb(DWZi{~ls5n1Q`I-LmTHWc<`$H;#v z;CO#P?xOJ3xfYpke617o@@2VjzI0YKHio0ls{zWFs-s&w6xyhi+k9ENfGkVT0STqA zzI#otjgwY!<#0m-V4;KTaFo~r@>DabQfnqHdM+ubPc}JnVqqiVF#0F z|4tukc2#@7(4BiiXz5-*vtC{JR$v<;*=Nb`kk>ZMP4FB~tMUYf8x&huLSrlH;XW3(v z;t>~6I=Zy;=$unX$L-}w?~>$#HMs(nEzPRtO#LY_Z>eFV{AQuF?d;Xu^0#9iN=dIS z`4AlCD1d<_(x)Sd6_sjfw(r77HHrKg#dmf?INqM`psVqQR@cilE?cj*mc2R5KSbN@ zSfkz}OEtm^iX9jWW*0);0?gUA)3oxoY4E$0jkRB7?ccuZ=pIrV7nTZs(K{fg0O^li zwD)oo2wJta+)1nZ^fYuVc9Ns~+V%kKi4S32K3RieV$x}j$YWO%ZF_vHlRLaLG7 zO>Ez(G=H2PIKxNf#;^rnx2Z1JHp|;*E^~VZh^lCa3%Fl0eP_&fRzZ+!IRrY5-Mre_ z!3Q@BJ?gtX8?58>A>%8q27w# zwwq8LHdTgJ{wIq~Q4*u;_H&JyfuH#ujQV;vud!?pB=Gge0AS3pA)T=fPA`Wk0tzhU zPSd=)UCU7sYR3xnur<%tIZR-oIB9#O^i+s#;@fxHvP# zc9aIBwhA6QC_kSHZb4Cf-;7o-S5id-TO5;i0Q3BDuv{Y+iH2^*qKKP z0N@RK@By$Q_367%Vxkfd!vt9jB@Q|#A~YghX}Lag-2&B!ds3EPR4X0Mqv=7Jx70+~ zhUST!k_wOsXTp1IPk;QN3e@Hw?L}j;mHbqn^HeN8%j?@xFUSamgYEVk>JCns7<|V> z^@0*9siH|!lH-2CT8o%SjG?;tRGG`TO6GFytGU@wgZ1HMmfNRal2xx9*41~&{7l@S zGr5h)SNNpC9bO;bRpOqnW-@5 z?ErLSh4Q2c8h2IIxi)c0D0*{oSm=dk0AKMmY- zyB`3SpPz~`h2?Em9CKMmEJcqGoyWgoHDPJfP*-VaDjfmv(#91K9~YUFrM`nZ6?uv8 ziD%W!uezEM)Ho`)(K|4dTDszf zIqUSCIwUJS3rb(+HGfTvALBIf0pVoli_*GOPCJ_&eqKv7q2x^|DZ^I>zsNb-d6wzr z!xs(ch~T#t+H9HW{yBbK+xSq}^d05bJy+fxT~h0IVsi znMz18R5e-B))><{Nh%dpDp2P;YXUZPIz$?-=@H%LE zWOnvDA&S_VJ#_u!>XK9Lhj7yMM0~>IF{aX|+k66`@1X~i`&(-R*>A7bwz{6s91|T~ z7+f6QC+--MBjiu5DC&1ab-urtbGbNp{E)0Nz4k0>Ve!NLD>i+}*Pq15i(|NJe8ZV1 z^Ssw$sQ@^u-*}vz4pFq0c1R`^O-0-TB|~6REbhB&Au+t>>P~jO*$uLjGWSZN2U6Ile72;!+96wx{ZMa?0Ycgd# zuCOe(0KTL*9?s^~d3Lk>pLZN`3l?W_fE_V_w?dzD!v0}4WjOrrd z<{Xh9+Ez?7P_TAqsx``_Uszn6WCF;8X3iDXuAT-RQLWN>c1c*jyZ> zc%9c-s&FoxEQ29CWK2#a*c3p7HcDOF;9ykP2&_?T`+4NxTt>Hx%21+(eI?8;oZMrk z`SxhXQEh(WfP-gJ|Aap-sb^!-=4=>XuS{^^=Mn7x6yW2Esz_nd)mW?63S8=?{lHW| z$`?JI=(;xmX}*|@@Nrd@Reb*0g_&ZNg2PVqt;h{7eKF=tugH7lFL|jOtVk*S0wSSf ze5%`e@5;if-ZImV6m-})Zmt}w3V4RrrCQBgoI${cK3e zfYh$1?t`NBN2^!>0Y#)t9s{c%njM9Sf|m8WP3P~OxXaeqaOE1zK+jHTtuo0yvi;L$dUTF9$jR_`QEAG;)0{TS1cgi(AclLU<|3;rW>k( z*X}bcpjQHcrk^ zQ)HHK=qa(Vw?LFI@tk3$C7!DA1#VCciheO)h1)qYAXpfW*Q#InD~XV>@b{v;-Rmj7 zmZ6Pq9b5u_xpp>rspo8TV_W3!`8f0+I-!^8V~}SgNqg>kfFRIl(c%$*w#@J&MwDr5 zmf!90OcO+eV~M6tSzHM{@+bXpT0tG!+fnBl4I`KFj;ZIxaqgsi3AmO!#Y$a+COrqG zBGN}@c0dyZpC+mlWVWA!sSUv~g#?#Th^;>ed-_Tb=p4c~IXrllJ2j@KK4%=zyOih& zS4U>lB-S~DC^lJa&Wc{SA>FgIL+sXMz{^FG+ad??=rKNKVf|wKA9zd`B|`NxMJ3Q; z-*?x);jT0Z|1^aXC+eB+`Ax=HG8(p-i)hKRnL$X<@^LdbME}5P6Fr2S+twdg*n8(C z#>0{!8e2~C2q&gB#$Kf_AH6jHZ839mVsb*xFJjX(yyEcYw5wdB8 zo^ZD^*z$OVyRw|Sr`ArtoPBav6D4UHdNCwdJgxYpjEN$a3u?JQ1u;YOb0W)ZZj(PB z3}0G)gFNi)sYg2VzAk0Qaz3#XRmp1$a)cpMNF-*Z<6naIsMmcyOG)j zXek8q65qpIPKsfu%hWZKSJw1SLo>6Z^x+i!*p)CYGg-#Kh1oXgt-q|g6B864@^L1j z!_uMSr#4+=~INCXjjN zHE{+Y*e@g{VFX|{h0`C4iS@2cpL@+wv! zAChfXfLcEwo=%x98#7|cLIx?5ya~G-E?0K}T5mtNwy`lbwx9$xY5S&KDKB==%0DH{ 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 + Easy to Use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/img/undraw_docusaurus_react.svg b/docs/static/img/undraw_docusaurus_react.svg new file mode 100644 index 0000000000..94b5cf08f8 --- /dev/null +++ b/docs/static/img/undraw_docusaurus_react.svg @@ -0,0 +1,170 @@ + + Powered by React + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/img/undraw_docusaurus_tree.svg b/docs/static/img/undraw_docusaurus_tree.svg new file mode 100644 index 0000000000..d9161d3392 --- /dev/null +++ b/docs/static/img/undraw_docusaurus_tree.svg @@ -0,0 +1,40 @@ + + Focus on What Matters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jest.config.js b/jest.config.js index 204d6f8851..75e0cc5b4d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -44,6 +44,7 @@ export default { '\\.svg\\?react$': '/scripts/__mocks__/fileMock.js', '\\.svg$': '/scripts/__mocks__/fileMock.js', '^@pdfme/generator$': '/scripts/__mocks__/@pdfme/generator.ts', + '\\.(css|less|scss|sass)$': 'identity-obj-proxy', }, moduleFileExtensions: [ 'web.js', @@ -68,7 +69,6 @@ export default { 'src/components/AddOn/support/services/Render.helper.ts', 'src/components/SecuredRoute/SecuredRoute.tsx', 'src/reportWebVitals.ts', - 'src/screens/UserPortal/Volunteer/Actions/Actions.spec.tsx', ], coverageThreshold: { global: { @@ -80,7 +80,6 @@ export default { '/node_modules/', '/build/', '/public/', - '/src/screens/UserPortal/Volunteer/Actions/Actions.spec.tsx', ], coverageDirectory: './coverage/jest', coverageReporters: ['text', 'html', 'text-summary', 'lcov'], diff --git a/package-lock.json b/package-lock.json index 3eb85993c3..7f115d04df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@mui/x-charts": "^7.22.2", "@mui/x-data-grid": "^7.22.1", "@mui/x-date-pickers": "^7.18.0", + "@pdfme/common": "^5.2.11", "@pdfme/generator": "^5.2.3", "@pdfme/schemas": "^5.1.6", "@reduxjs/toolkit": "^2.3.0", @@ -30,7 +31,7 @@ "babel-plugin-transform-import-meta": "^2.2.1", "bootstrap": "^5.3.3", "chart.js": "^4.4.6", - "cross-env": "^7.0.3", + "customize-cra": "^1.0.0", "dayjs": "^1.11.13", "dotenv": "^16.4.5", "flag-icons": "^7.2.3", @@ -41,7 +42,7 @@ "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.1", - "inquirer": "^8.0.0", + "inquirer": "^11.0.2", "js-cookie": "^3.0.1", "lcov-result-merger": "^5.0.1", "markdown-toc": "^1.2.0", @@ -71,7 +72,6 @@ "vite": "^5.4.8", "vite-plugin-environment": "^1.1.3", "vite-plugin-node-polyfills": "^0.22.0", - "vite-plugin-svgr": "^4.2.0", "vite-tsconfig-paths": "^5.1.3", "web-vitals": "^4.2.4" }, @@ -80,6 +80,7 @@ "@babel/preset-env": "^7.26.0", "@babel/preset-react": "^7.25.7", "@babel/preset-typescript": "^7.26.0", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^12.1.10", @@ -119,6 +120,7 @@ "postcss-modules": "^6.0.0", "sass": "^1.80.7", "tsx": "^4.19.1", + "vite-plugin-svgr": "^4.2.0", "vitest": "^2.1.5", "whatwg-fetch": "^3.6.20" }, @@ -154,6 +156,141 @@ "node": ">=6.0.0" } }, + "node_modules/@ant-design/colors": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.1.0.tgz", + "integrity": "sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ctrl/tinycolor": "^3.6.1" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.22.1.tgz", + "integrity": "sha512-SLuXM4wiEE1blOx94iXrkOgseMZHzdr4ngdFu3VVDq6AOWh7rlwqTkMAtJho3EsBF6x/eUGOtK53VZXGQG7+sQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/cssinjs/node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/cssinjs/node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/cssinjs/node_modules/stylis": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz", + "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.5.2.tgz", + "integrity": "sha512-xc53rjVBl9v2BqFxUjZGti/RfdDeA8/6KYglmInM2PNqSXc/WfuGDTifJI/ZsokJK0aeKvOIbXc9y2g8ILAhEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@ant-design/react-slick/node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.22" + } + }, "node_modules/@apollo/client": { "version": "3.11.8", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.11.8.tgz", @@ -2063,6 +2200,16 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@dicebear/adventurer": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/@dicebear/adventurer/-/adventurer-9.2.2.tgz", @@ -3157,6 +3304,239 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@inquirer/checkbox": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", + "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", + "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz", + "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", + "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.9.tgz", + "integrity": "sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz", + "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz", + "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz", + "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz", + "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^3.0.1", + "@inquirer/confirm": "^4.0.1", + "@inquirer/editor": "^3.0.1", + "@inquirer/expand": "^3.0.1", + "@inquirer/input": "^3.0.1", + "@inquirer/number": "^2.0.1", + "@inquirer/password": "^3.0.1", + "@inquirer/rawlist": "^3.0.1", + "@inquirer/search": "^2.0.1", + "@inquirer/select": "^3.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz", + "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz", + "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz", + "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3669,17 +4049,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -4231,6 +4600,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "dev": true, "hasInstallScript": true, "optional": true, "dependencies": { @@ -4269,6 +4639,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -4288,6 +4659,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -4307,6 +4679,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -4326,6 +4699,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -4345,6 +4719,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4364,6 +4739,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4383,6 +4759,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4402,6 +4779,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4421,6 +4799,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4440,6 +4819,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4459,6 +4839,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -4478,6 +4859,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -4497,6 +4879,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -4528,17 +4911,19 @@ } }, "node_modules/@pdfme/common": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@pdfme/common/-/common-1.2.6.tgz", - "integrity": "sha512-ROmQ/iMUdmFS2QXD/kKDdcU5T6H3azDs2b1hE/OXs8531BPZ9ABbu9+1NRZQoNK4U/zP2F+Osb/B8ckr9lAmGg==", - "peer": true, + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@pdfme/common/-/common-5.2.11.tgz", + "integrity": "sha512-XfVn3UH0LRRzUu9NDJ0rUzxv5Nib+vyTEWiTv3KNUuO/X2553yIpzAAlrn46V+0QhW+491G+zgbr6ark0cJe4w==", + "license": "MIT", "dependencies": { + "@pdfme/pdf-lib": "^1.18.3", + "acorn": "^8.14.0", "buffer": "^6.0.3", - "fontkit": "^2.0.2", "zod": "^3.20.2" }, - "engines": { - "node": ">=14" + "peerDependencies": { + "antd": "^5.11.2", + "form-render": "^2.2.20" } }, "node_modules/@pdfme/generator": { @@ -4622,18 +5007,176 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@react-aria/ssr": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.5.tgz", - "integrity": "sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==", + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "license": "MIT", + "peer": true, "dependencies": { - "@swc/helpers": "^0.5.0" + "@babel/runtime": "^7.24.4" }, "engines": { - "node": ">= 12" + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz", + "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.5.tgz", + "integrity": "sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" } }, "node_modules/@react-aria/ssr/node_modules/@swc/helpers": { @@ -5122,6 +5665,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, "engines": { "node": ">=14" }, @@ -5137,6 +5681,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, "engines": { "node": ">=14" }, @@ -5350,7 +5895,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5370,7 +5915,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5387,7 +5931,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -5402,7 +5945,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -5414,8 +5956,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@testing-library/jest-dom": { "version": "6.6.3", @@ -5500,8 +6041,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5584,18 +6124,6 @@ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" }, - "node_modules/@types/eslint": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.0.tgz", - "integrity": "sha512-gsF+c/0XOguWgaOgvFs+xnnRqt9GwgTvIks36WpE6ueeI4KCEHHd8K/CKHqhOqrJKsYH8m27kRzQEvWXAwXUTw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -5702,11 +6230,19 @@ "@types/unist": "*" } }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.9.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", - "devOptional": true, "dependencies": { "undici-types": "~6.19.8" } @@ -5904,6 +6440,12 @@ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "16.0.5", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", @@ -6380,6 +6922,7 @@ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/spy": "2.1.5", "@vitest/utils": "2.1.5", @@ -6395,6 +6938,7 @@ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/spy": "2.1.5", "estree-walker": "^3.0.3", @@ -6417,10 +6961,11 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", - "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", "dev": true, + "license": "MIT", "dependencies": { "tinyrainbow": "^1.2.0" }, @@ -6433,6 +6978,7 @@ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/utils": "2.1.5", "pathe": "^1.1.2" @@ -6446,6 +6992,7 @@ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/pretty-format": "2.1.5", "magic-string": "^0.30.12", @@ -6455,11 +7002,25 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", + "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vitest/spy": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", "dev": true, + "license": "MIT", "dependencies": { "tinyspy": "^3.0.2" }, @@ -6472,6 +7033,7 @@ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/pretty-format": "2.1.5", "loupe": "^3.1.2", @@ -6481,6 +7043,19 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/utils/node_modules/@vitest/pretty-format": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", + "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@wry/caches": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz", @@ -6532,10 +7107,10 @@ "dev": true }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "devOptional": true, + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -6584,6 +7159,16 @@ "node": ">=0.4.0" } }, + "node_modules/add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "license": "MIT", + "peer": true, + "dependencies": { + "object-assign": "4.x" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -6596,6 +7181,30 @@ "node": ">= 6.0.0" } }, + "node_modules/ahooks": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.8.4.tgz", + "integrity": "sha512-39wDEw2ZHvypaT14EpMMk4AzosHWt0z9bulY0BeDsvc9PqJEV+Kjh/4TZfftSsotBMq52iYIOFPd3PR56e0ZJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/air-datepicker": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/air-datepicker/-/air-datepicker-3.5.3.tgz", @@ -6682,6 +7291,82 @@ "node": ">=0.10.0" } }, + "node_modules/antd": { + "version": "5.22.6", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.22.6.tgz", + "integrity": "sha512-ZYURSV3FR8qQgbfpa554thlO07L6PeHwhAM0wmxnobOBogND/HqSnTU+UZTqT2b2y9MxSfAIu5Xn1uEM9UpceQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^7.1.0", + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/icons": "^5.5.2", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.25.7", + "@ctrl/tinycolor": "^3.6.1", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.2.6", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.30.0", + "rc-checkbox": "~3.3.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.2.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.0", + "rc-image": "~7.11.0", + "rc-input": "~1.6.4", + "rc-input-number": "~9.3.0", + "rc-mentions": "~2.17.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.2", + "rc-pagination": "~5.0.0", + "rc-picker": "~4.8.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.0", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.5.0", + "rc-select": "~14.16.4", + "rc-slider": "~11.1.7", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.49.0", + "rc-tabs": "~15.4.0", + "rc-textarea": "~1.8.2", + "rc-tooltip": "~6.2.1", + "rc-tree": "~5.10.1", + "rc-tree-select": "~5.24.5", + "rc-upload": "~4.8.1", + "rc-util": "^5.44.2", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.22" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -6894,10 +7579,18 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } }, + "node_modules/async-validator": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz", + "integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==", + "license": "MIT", + "peer": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7288,6 +7981,17 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "license": "MIT", + "peer": true, + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -7321,39 +8025,6 @@ "node": ">=8" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -7657,7 +8328,6 @@ "url": "https://feross.org/support" } ], - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -7692,6 +8362,7 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -7815,6 +8486,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -7869,7 +8541,8 @@ "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "license": "MIT" }, "node_modules/chart.js": { "version": "4.4.6", @@ -7888,6 +8561,7 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 16" } @@ -7953,9 +8627,10 @@ "dev": true }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" }, "node_modules/cli-boxes": { "version": "2.2.1", @@ -7984,17 +8659,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -8062,11 +8726,12 @@ } }, "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/cliui": { @@ -8211,6 +8876,29 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/component-classes": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", + "integrity": "sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==", + "license": "MIT", + "peer": true, + "dependencies": { + "component-indexof": "0.0.3" + } + }, + "node_modules/component-indexof": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", + "integrity": "sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==", + "peer": true + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==", + "license": "MIT", + "peer": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -8341,11 +9029,30 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, - "node_modules/core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", - "dev": true, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "peer": true, + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT", + "peer": true + }, + "node_modules/core-js-compat": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "dev": true, "dependencies": { "browserslist": "^4.23.3" }, @@ -8413,6 +9120,17 @@ "sha.js": "^2.4.8" } }, + "node_modules/create-react-class": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -8422,6 +9140,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, "dependencies": { "cross-spawn": "^7.0.1" }, @@ -8447,6 +9166,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -8490,6 +9210,17 @@ "node": ">=8" } }, + "node_modules/css-animation": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", + "license": "MIT", + "peer": true, + "dependencies": { + "babel-runtime": "6.x", + "component-classes": "^1.2.5" + } + }, "node_modules/css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", @@ -8545,6 +9276,14 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/customize-cra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/customize-cra/-/customize-cra-1.0.0.tgz", + "integrity": "sha512-DbtaLuy59224U+xCiukkxSq8clq++MOtJ1Et7LED1fLszWe88EoblEYFBJ895sB1mC6B4uu3xPT/IjClELhMbA==", + "dependencies": { + "lodash.flow": "^3.5.0" + } + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -8775,6 +9514,7 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -8802,25 +9542,6 @@ "node": ">=0.10.0" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -8915,6 +9636,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, "optional": true, "bin": { "detect-libc": "bin/detect-libc.js" @@ -8988,7 +9710,13 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, + "dev": true + }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==", + "license": "MIT", "peer": true }, "node_modules/dom-helpers": { @@ -9087,6 +9815,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -9183,16 +9912,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -9341,7 +10060,8 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.0.0", @@ -10116,6 +10836,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -10282,6 +11003,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -10295,6 +11017,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -10380,28 +11103,6 @@ "bser": "2.1.1" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -10599,6 +11300,129 @@ "node": ">= 6" } }, + "node_modules/form-render": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-render/-/form-render-2.5.1.tgz", + "integrity": "sha512-oNbJ+McqB5h1yuyxYAT3ixJF8itmHlnKvqDgQhJT9Tw1c3yGwfRnVXboRxBV+Myz0dkf47zL6lyY1l74yQsWsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/icons": "^4.0.2", + "ahooks": "^3.7.5", + "async-validator": "^3.5.1", + "classnames": "^2.3.1", + "color": "^3.1.2", + "dayjs": "^1.11.7", + "lodash-es": "^4.17.21", + "rc-color-picker": "^1.2.6", + "virtualizedtableforantd4": "^1.1.2", + "zustand": "^4.1.5" + }, + "peerDependencies": { + "antd": "4.x || 5.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/form-render/node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/form-render/node_modules/@ant-design/icons": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz", + "integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.3.0", + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "lodash": "^4.17.15", + "rc-util": "^5.9.4" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/form-render/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/form-render/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/form-render/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT", + "peer": true + }, + "node_modules/form-render/node_modules/rc-color-picker": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rc-color-picker/-/rc-color-picker-1.2.6.tgz", + "integrity": "sha512-AaC9Pg7qCHSy5M4eVbqDIaNb2FC4SEw82GOHB2C4R/+vF2FVa/r5XA+Igg5+zLPmAvBLhz9tL4MAfkRA8yWNJw==", + "license": "MIT", + "peer": true, + "dependencies": { + "classnames": "^2.2.5", + "prop-types": "^15.5.8", + "rc-trigger": "1.x", + "rc-util": "^4.0.2", + "tinycolor2": "^1.4.1" + }, + "peerDependencies": { + "react": "16.x", + "react-dom": "16.x" + } + }, + "node_modules/form-render/node_modules/rc-color-picker/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "peer": true, + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/form-render/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -10995,6 +11819,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -11332,19 +12157,6 @@ "cross-fetch": "4.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -11362,6 +12174,7 @@ "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", "dev": true, + "license": "MIT", "dependencies": { "harmony-reflect": "^1.4.6" }, @@ -11411,7 +12224,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.2.tgz", "integrity": "sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==", - "devOptional": true + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -11496,79 +12309,22 @@ "dev": true }, "node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-11.1.0.tgz", + "integrity": "sha512-CmLAZT65GG/v30c+D2Fk8+ceP6pxD6RL+hIUOWAltCmeyEqWYwqu9v76q03OvjyZ3AB0C1Ala2stn1z/rMqGEw==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@inquirer/core": "^9.2.1", + "@inquirer/prompts": "^6.0.1", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "ansi-escapes": "^4.3.2", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "rxjs": "^7.8.1" }, "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/internal-slot": { @@ -11593,6 +12349,13 @@ "node": ">=12" } }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -11874,14 +12637,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -12096,17 +12851,6 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -12183,7 +12927,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/isobject": { "version": "3.0.1", @@ -13918,6 +14663,16 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "string-convert": "^0.2.0" + } + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -14441,6 +15196,13 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT", + "peer": true + }, "node_modules/lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -14458,6 +15220,11 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -14481,36 +15248,6 @@ "lodash._reinterpolate": "^3.0.0" } }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", @@ -14670,12 +15407,14 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, "dependencies": { "tslib": "^2.0.3" } @@ -14707,7 +15446,6 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -15021,6 +15759,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, "engines": { "node": ">=6" } @@ -15121,9 +15860,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/nanoid": { "version": "3.3.7", @@ -15152,6 +15895,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -15161,6 +15905,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, "optional": true }, "node_modules/node-fetch": { @@ -15536,6 +16281,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -15613,66 +16359,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -15682,6 +16368,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -15859,6 +16546,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -15902,13 +16590,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.16" } @@ -15928,6 +16618,13 @@ "node": ">=0.12" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "peer": true + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -16403,115 +17100,870 @@ "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", "dev": true, "dependencies": { - "escape-goat": "^2.0.0" + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qs": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "peer": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, + "node_modules/randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dependencies": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-align": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz", + "integrity": "sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==", + "license": "MIT", + "peer": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "dom-align": "^1.7.0", + "prop-types": "^15.5.8", + "rc-util": "^4.0.4" + } + }, + "node_modules/rc-align/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "peer": true, + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-align/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/rc-animate": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.11.1.tgz", + "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "babel-runtime": "6.x", + "classnames": "^2.2.6", + "css-animation": "^1.3.2", + "prop-types": "15.x", + "raf": "^3.4.0", + "rc-util": "^4.15.3", + "react-lifecycles-compat": "^3.0.4" + } + }, + "node_modules/rc-animate/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "peer": true, + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-animate/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/rc-cascader": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.30.0.tgz", + "integrity": "sha512-rrzSbk1Bdqbu+pDwiLCLHu72+lwX9BZ28+JKzoi0DWZ4N29QYFeip8Gctl33QVd2Xg3Rf14D3yAOG76ElJw16w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.10.1", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.3.0.tgz", + "integrity": "sha512-Ih3ZaAcoAiFKJjifzwsGiT/f/quIkxJoklW4yKGho14Olulwn8gN7hOBve0/WGDg5o/l/5mL0w7ff7/YGvefVw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.2.0.tgz", + "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.0.tgz", + "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.11.0.tgz", + "integrity": "sha512-aZkTEZXqeqfPZtnSdNUnKQA0N/3MbgR7nUnZ+/4MfSFWPFHZau4p5r5ShaI0KPEMnNjv4kijSCFq/9wtJpwykw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.6.4.tgz", + "integrity": "sha512-lBZhfRD4NSAUW0zOKLUeI6GJuXkxeZYi0hr8VcJgJpyTNOvHw1ysrKWAHcEOAAHj7guxgmWYSi6xWrEdfrSAsA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.3.0.tgz", + "integrity": "sha512-JQ363ywqRyxwgVxpg2z2kja3CehTpYdqR7emJ/6yJjRdbvo+RvfE83fcpBCIJRq3zLp8SakmEXq60qzWyZ7Usw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.6.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.17.0.tgz", + "integrity": "sha512-sfHy+qLvc+p8jx8GUsujZWXDOIlIimp6YQz7N5ONQ6bHsa2kyG+BLa5k2wuxgebBbH97is33wxiyq5UkiXRpHA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.6.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.8.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.0.tgz", + "integrity": "sha512-vAL0yqPkmXWk3+YKRkmIR8TYj3RVdEt3ptG2jCJXWNAvQbT0VJJdRyHZ7kG/l1JsZlB+VJq/VcYOo69VR4oD+w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.2.tgz", + "integrity": "sha512-Id4IYMoii3zzrG0lB0gD6dPgJx4Iu95Xu0BQrhHIbp7ZnAZbLqdqQ73aIWH0d0UFcElxwaKjnzNovTjo7kXz7g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.2.tgz", + "integrity": "sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.0.0.tgz", + "integrity": "sha512-QjrPvbAQwps93iluvFM62AEYglGYhWW2q/nliQqmvkTi4PXP4HHoh00iC1Sa5LLVmtWQHmG73fBi2x6H6vFHRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.8.3.tgz", + "integrity": "sha512-hJ45qoEs4mfxXPAJdp1n3sKwADul874Cd0/HwnsEOE60H+tgiJUGgbOD62As3EG/rFVNS5AWRfBCDJJfmRqOVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.0.tgz", + "integrity": "sha512-oxvx1Q5k5wD30sjN5tqAyWTvJfLNNJn7Oq3IeS4HxWfAiC4BOXMITNAsw7u/fzdtO4MS8Ki8uRLOzcnEuoQiAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.5.0.tgz", + "integrity": "sha512-B28Fe3J9iUFOhFJET3RoXAPFJ2u47QvLSYcZWC4tFYNGPEjug5LAxEasZlA/PpAxhdOPqGWsGbSj7ftneukJnw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.4", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.4.tgz", + "integrity": "sha512-jP6qf7+vjnxGvPpfPWbGYfFlSl3h8L2XcD4O7g2GYXmEeBC0mw+nPD7i++OOE8v3YGqP8xtYjRKAWCMLfjgxlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.7", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.7.tgz", + "integrity": "sha512-ytYbZei81TX7otdC0QvoYD72XSlxvTihNth5OeZ6PMXyEDq/vHdWFulQmfDGyXK1NwKwSlKgpvINOa88uT5g2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.49.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.49.0.tgz", + "integrity": "sha512-/FoPLX94muAQOxVpi1jhnpKjOIqUbT81eELQPAzSXOke4ky4oCWYUXOcVpL31ZCO90xScwVSXRd7coqtgtB1Ng==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.41.0", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.4.0.tgz", + "integrity": "sha512-llKuyiAVqmXm2z7OrmhX5cNb2ueZaL8ZyA2P4R+6/72NYYcbEgOXibwHiQCFY2RiN3swXl53SIABi2CumUS02g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.8.2.tgz", + "integrity": "sha512-UFAezAqltyR00a8Lf0IPAyTd29Jj9ee8wt8DqXyDMal7r/Cg/nDt3e1OOv3Th4W6mKaZijjgwuPXhAfVNTN8sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.6.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.2.1.tgz", + "integrity": "sha512-rws0duD/3sHHsD905Nex7FvoUGy2UBQRhTkKxeEvr2FB+r21HsOxcDJI0TzyO8NHhnAA8ILr8pfbSBg5Jj5KBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/qs": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", - "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", + "node_modules/rc-tree": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.10.1.tgz", + "integrity": "sha512-FPXb3tT/u39mgjr6JNlHaUTYfHkVGW56XaGDahDpEFLGsnPxGcVLNTjcqoQb/GNbSCycl7tD7EvIymwOTP0+Yw==", + "license": "MIT", + "peer": true, "dependencies": { - "side-channel": "^1.0.6" + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" }, "engines": { - "node": ">=0.6" + "node": ">=10.x" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "react": "*", + "react-dom": "*" } }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "engines": { - "node": ">=0.4.x" + "node_modules/rc-tree-select": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.24.5.tgz", + "integrity": "sha512-PnyR8LZJWaiEFw0SHRqo4MNQWyyZsyMs8eNmo68uXZWjxc7QqeWcjPPoONN0rc90c3HZqGF9z+Roz+GLzY5GXA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.10.1", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" - }, - "node_modules/randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "node_modules/rc-trigger": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-1.11.5.tgz", + "integrity": "sha512-MBuUPw1nFzA4K7jQOwb7uvFaZFjXGd00EofUYiZ+l/fgKVq8wnLC0lkv36kwqM7vfKyftRo2sh7cWVpdPuNnnw==", + "peer": true, "dependencies": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "engines": { - "node": ">= 0.10.0" + "babel-runtime": "6.x", + "create-react-class": "15.x", + "prop-types": "15.x", + "rc-align": "2.x", + "rc-animate": "2.x", + "rc-util": "4.x" } }, - "node_modules/randomatic/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "engines": { - "node": ">=0.10.0" + "node_modules/rc-trigger/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "peer": true, + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/rc-trigger/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/rc-upload": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.8.1.tgz", + "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==", + "license": "MIT", + "peer": true, "dependencies": { - "safe-buffer": "^5.1.0" + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "node_modules/rc-util": { + "version": "5.44.3", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.3.tgz", + "integrity": "sha512-q6KCcOFk3rv/zD3MckhJteZxb0VjAIFuf622B7ElK4vfrZdAzs16XR5p3VTdy3+U5jfJU5ACz4QnhLSuAGe5dA==", + "license": "MIT", + "peer": true, "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, + "node_modules/rc-virtual-list": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.16.1.tgz", + "integrity": "sha512-algM5UsB7vrlPNr9lsZEH8s9KHkP8XbT/Y0qylyPkiM8mIOlSJLjBNADcmbYPEQCm4zW82mZRJuVHNzqqN0EAQ==", + "license": "MIT", + "peer": true, "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" }, - "bin": { - "rc": "cli.js" + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, "node_modules/rc/node_modules/strip-json-comments": { @@ -16676,6 +18128,13 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT", + "peer": true + }, "node_modules/react-google-recaptcha": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz", @@ -16941,6 +18400,13 @@ "node": ">=4" } }, + "node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "license": "MIT", + "peer": true + }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", @@ -17108,6 +18574,13 @@ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT", + "peer": true + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -17382,9 +18855,10 @@ ] }, "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -17503,7 +18977,7 @@ "version": "1.80.7", "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.7.tgz", "integrity": "sha512-MVWvN0u5meytrSjsU7AWsbhoXi1sc58zADXFllfZzbsBT1GHjjar6JwBINYPRrkx/zqnQ6uqbQuHgE95O+C+eQ==", - "devOptional": true, + "dev": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -17523,7 +18997,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", - "devOptional": true, + "dev": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -17538,7 +19012,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", - "devOptional": true, + "dev": true, "engines": { "node": ">= 14.16.0" }, @@ -17567,6 +19041,29 @@ "loose-envify": "^1.1.0" } }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -17660,10 +19157,18 @@ "sha.js": "bin.js" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT", + "peer": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -17675,6 +19180,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -17718,7 +19224,8 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true }, "node_modules/simple-swizzle": { "version": "0.2.2", @@ -17808,6 +19315,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -17833,7 +19341,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, + "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -17843,7 +19351,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -17941,6 +19449,13 @@ "node": ">=0.6.19" } }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT", + "peer": true + }, "node_modules/string-hash": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", @@ -18175,6 +19690,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -18209,7 +19725,8 @@ "node_modules/svg-parser": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true }, "node_modules/symbol-observable": { "version": "4.0.0", @@ -18262,32 +19779,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser": { - "version": "5.32.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.32.0.tgz", - "integrity": "sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ==", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "optional": true, - "peer": true - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -18322,11 +19813,6 @@ "node": ">=8" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -18395,6 +19881,13 @@ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT", + "peer": true + }, "node_modules/tinyexec": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", @@ -18424,6 +19917,7 @@ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -18432,6 +19926,7 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -18487,6 +19982,13 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT", + "peer": true + }, "node_modules/toml": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", @@ -19272,8 +20774,7 @@ "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "devOptional": true + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", @@ -19634,6 +21135,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/virtualizedtableforantd4": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/virtualizedtableforantd4/-/virtualizedtableforantd4-1.3.1.tgz", + "integrity": "sha512-rW8KoToI2nt1jNtweXIUIiygi74XMzKLzUrrtZbGsQc7m3v68AaedPuf4CZcte+nosgYuPEWnAgjuI/KR8BVbg==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "antd": "^4.0.0 || ^5.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { "version": "5.4.8", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", @@ -19697,6 +21210,7 @@ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", @@ -19741,6 +21255,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", + "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.5", "@svgr/core": "^8.1.0", @@ -19754,6 +21269,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, "engines": { "node": ">=14" }, @@ -19769,6 +21285,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, "engines": { "node": ">=14" }, @@ -19784,6 +21301,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, "engines": { "node": ">=14" }, @@ -19799,6 +21317,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, "engines": { "node": ">=14" }, @@ -19814,6 +21333,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, "engines": { "node": ">=14" }, @@ -19829,6 +21349,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, "engines": { "node": ">=12" }, @@ -19844,6 +21365,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", @@ -19869,6 +21391,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -19888,6 +21411,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, "dependencies": { "@babel/types": "^7.21.3", "entities": "^4.4.0" @@ -19904,6 +21428,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -19924,12 +21449,14 @@ "node_modules/vite-plugin-svgr/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/vite-plugin-svgr/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "engines": { "node": ">=10" }, @@ -19941,6 +21468,7 @@ "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", @@ -19966,6 +21494,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -20015,6 +21544,7 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/expect": "2.1.5", "@vitest/mocker": "2.1.5", @@ -20127,14 +21657,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/web-vitals": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", @@ -20200,6 +21722,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -20475,6 +21998,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", @@ -20492,11 +22027,39 @@ "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zustand": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz", + "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "use-sync-external-store": "1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index da331b88d2..257949ead6 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@mui/x-charts": "^7.22.2", "@mui/x-data-grid": "^7.22.1", "@mui/x-date-pickers": "^7.18.0", + "@pdfme/common": "^5.2.11", "@pdfme/generator": "^5.2.3", "@pdfme/schemas": "^5.1.6", "@reduxjs/toolkit": "^2.3.0", @@ -38,7 +39,7 @@ "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.1", - "inquirer": "^8.0.0", + "inquirer": "^11.0.2", "js-cookie": "^3.0.1", "lcov-result-merger": "^5.0.1", "markdown-toc": "^1.2.0", @@ -86,7 +87,7 @@ "format:check": "prettier --check \"**/*.{ts,tsx,json,scss,css}\"", "check-tsdoc": "node .github/workflows/check-tsdoc.js", "typecheck": "tsc --project tsconfig.json --noEmit", - "prepare": "[ \"$NODE_ENV\" != \"production\" ] && husky install || exit 0", + "prepare": "husky install", "jest-preview": "jest-preview", "update:toc": "node scripts/githooks/update-toc.js", "lint-staged": "lint-staged --concurrent false", @@ -119,6 +120,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^12.1.10", + "@testing-library/dom": "^10.4.0", "@types/inquirer": "^9.0.7", "@types/jest": "^26.0.24", "@types/js-cookie": "^3.0.6", @@ -155,9 +157,9 @@ "postcss-modules": "^6.0.0", "sass": "^1.80.7", "tsx": "^4.19.1", + "vite-plugin-svgr": "^4.2.0", "vitest": "^2.1.5", - "whatwg-fetch": "^3.6.20", - "vite-plugin-svgr": "^4.2.0" + "whatwg-fetch": "^3.6.20" }, "resolutions": { "@apollo/client": "^3.4.0-beta.19", diff --git a/public/images/svg/options-outline.svg b/public/images/svg/options-outline.svg new file mode 100644 index 0000000000..939935eb11 --- /dev/null +++ b/public/images/svg/options-outline.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/public/images/svg/organization.svg b/public/images/svg/organization.svg new file mode 100644 index 0000000000..9bed28f87e --- /dev/null +++ b/public/images/svg/organization.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 57b4c5de98..a6cb90d3d1 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -414,6 +414,7 @@ "recurringEvent": "Recurring Event", "isPublic": "Is Public", "isRegistrable": "Is Registrable", + "createChat": "Create Chat", "createEvent": "Create Event", "enterFilter": "Enter Filter", "enterTitle": "Enter Title", @@ -553,6 +554,7 @@ "recurringEvent": "Recurring Event", "isPublic": "Is Public", "isRegistrable": "Is Registrable", + "createChat": "Create Chat", "updatePost": "Update Post", "eventDetails": "Event Details", "eventDeleted": "Event deleted successfully.", @@ -772,6 +774,17 @@ "loading": "Loading...", "noAttendees": "Attendees not Found" }, + "eventRegistrant": { + "sort": "Sort", + "allRegistrants": "All Registrants", + "eventRegistrantsTable": "Event Registrants Table", + "serialNumber": "Serial Number", + "registrant": "Registrant", + "registeredAt": "Registered At", + "createdAt": "Created At", + "addRegistrant": "Add Registrant", + "noRegistrantsFound": "No Registrants Found." + }, "onSpotAttendee": { "title": "On-spot Attendee", "enterFirstName": "Enter First Name", @@ -1193,6 +1206,7 @@ "enterDescription": "Enter Description", "publicEvent": "Is Public", "registerable": "Is Registerable", + "createChat": "Create Chat", "monthlyCalendarView": "Monthly Calendar", "yearlyCalendarView": "Yearly Calender", "startTime": "startTime", @@ -1248,14 +1262,22 @@ "endOfResults": "endOfResults" }, "userChat": { + "add": "Add", "chat": "Chat", "search": "Search", "messages": "Messages", - "contacts": "Contacts" + "contacts": "Contacts", + "create": "Create", + "newChat": "New Chat", + "newGroupChat": "New Group Chat", + "groupInfo": "Group Info", + "members": "Members", + "addMembers": "Add Members" }, "userChatRoom": { "selectContact": "Select a contact to start conversation", - "sendMessage": "Send Message" + "sendMessage": "Send Message", + "reply": "Reply" }, "orgProfileField": { "loading": "Loading...", @@ -1405,6 +1427,9 @@ "userPledges": { "title": "My Pledges" }, + "leaveOrganization": { + "title": "Leave Organization" + }, "eventVolunteers": { "volunteers": "Volunteers", "volunteer": "Volunteer", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index d089aefeb5..416d00c4b4 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -439,7 +439,8 @@ "startDate": "Date de début", "endDate": "Date de fin", "talawaApiUnavailable": "API Talawa indisponible", - "done": "Fait" + "done": "Fait", + "createChat": "Créer une discussion" }, "organizationActionItems": { "actionItemCategory": "Catégorie de l'Action", @@ -579,7 +580,8 @@ "registerEvent": "Inscrire à l'événement", "close": "Fermer", "talawaApiUnavailable": "API Talawa non disponible", - "done": "Terminé" + "done": "Terminé", + "createChat": "Créer une discussion" }, "funds": { "title": "Fonds", @@ -1082,6 +1084,17 @@ "loading": "Chargement...", "noAttendees": "Aucun participant trouvé" }, + "eventRegistrant": { + "sort": "Trier", + "allRegistrants": "Tous les inscrits", + "eventRegistrantsTable": "Table des inscrits à l'événement", + "serialNumber": "Numéro de série", + "registrant": "Inscrit", + "registeredAt": "Enregistré le", + "createdAt": "Créé le", + "addRegistrant": "Ajouter un inscrit", + "noRegistrantsFound": "Aucun inscrit trouvé." + }, "onSpotAttendee": { "title": "Participant sur place", "enterFirstName": "Entrez le prénom", @@ -1204,7 +1217,8 @@ "eventDescription": "Description de l'événement", "eventLocation": "Lieu de l'événement", "startDate": "Date de début", - "endDate": "Date de fin" + "endDate": "Date de fin", + "createChat": "Créer une discussion" }, "userEventCard": { "starts": "Départs", @@ -1248,14 +1262,22 @@ "endOfResults": "Fin des résultats" }, "userChat": { + "add": "Ajouter", "chat": "Chat", "contacts": "Contacts", "search": "rechercher", - "messages": "messages" + "messages": "messages", + "create": "créer", + "newChat": "nouvelle discussion", + "newGroupChat": "Nouvelle discussion de groupe", + "groupInfo": "Informations sur le groupe", + "members": "Membres", + "addMembers": "Add Members" }, "userChatRoom": { "selectContact": "Sélectionnez un contact pour démarrer la conversation", - "sendMessage": "Envoyer le message" + "sendMessage": "Envoyer le message", + "reply": "répondre" }, "orgProfileField": { "loading": "Chargement...", @@ -1405,6 +1427,9 @@ "userPledges": { "title": "Mes Promesses" }, + "leaveOrganization": { + "title": "Quitter l'organisation" + }, "eventVolunteers": { "volunteers": "Bénévoles", "volunteer": "Bénévole", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 2645340b23..6abb8abefc 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -439,7 +439,8 @@ "startDate": "प्रारंभ तिथि", "endDate": "समाप्ति तिथि", "talawaApiUnavailable": "Talawa API अनुपलब्ध", - "done": "पूर्ण" + "done": "पूर्ण", + "createChat": "चैट बनाएं" }, "organizationActionItems": { "actionItemCategory": "क्रिया वस्तु श्रेणी", @@ -579,7 +580,8 @@ "registerEvent": "कार्यक्रम के लिए पंजीकरण करें", "close": "बंद करें", "talawaApiUnavailable": "Talawa API अनुपलब्ध", - "done": "समाप्त" + "done": "समाप्त", + "createChat": "चैट बनाएं" }, "funds": { "title": "फंड", @@ -1082,6 +1084,17 @@ "loading": "लोड हो रहा है", "noAttendees": "कोई प्रतिभागी नहीं मिला" }, + "eventRegistrant": { + "sort": "छांटें", + "allRegistrants": "सभी पंजीकृत व्यक्ति", + "eventRegistrantsTable": "इवेंट पंजीकृत व्यक्ति तालिका", + "serialNumber": "सिरियल नंबर", + "registrant": "पंजीकृत व्यक्ति", + "registeredAt": "पंजीकरण तिथि", + "createdAt": "निर्माण तिथि", + "addRegistrant": "पंजीकृत व्यक्ति जोड़ें", + "noRegistrantsFound": "कोई पंजीकृत व्यक्ति नहीं मिला" + }, "onSpotAttendee": { "title": "ऑन-स्पॉट प्रतिभागी", "enterFirstName": "प्रथम नाम दर्ज करें", @@ -1204,7 +1217,8 @@ "eventDescription": "कार्यक्रम विवरण", "eventLocation": "कार्यक्रम स्थान", "startDate": "प्रारंभ तिथि", - "endDate": "समाप्ति तिथि" + "endDate": "समाप्ति तिथि", + "createChat": "चैट बनाएं" }, "userEventCard": { "starts": "प्रारंभ होगा", @@ -1248,14 +1262,22 @@ "endOfResults": "परिणाम समाप्त" }, "userChat": { + "add": "जोड़ें", "chat": "बात करना", "contacts": "संपर्क", "search": "खोज", - "messages": "संदेश" + "messages": "संदेश", + "create": "बनाएं", + "newChat": "नई चैट", + "newGroupChat": "नया समूह चैट", + "groupInfo": "समूह जानकारी", + "members": "सदस्यों", + "addMembers": "सदस्य जोड़ें" }, "userChatRoom": { "selectContact": "बातचीत शुरू करने के लिए एक संपर्क चुनें", - "sendMessage": "मेसेज भेजें" + "sendMessage": "मेसेज भेजें", + "reply": "जवाब" }, "orgProfileField": { "loading": "लोड हो रहा है...", @@ -1405,6 +1427,9 @@ "userPledges": { "title": "मेरी प्रतिज्ञाएँ" }, + "leaveOrganization": { + "title": "संगठन छोड़ें" + }, "eventVolunteers": { "volunteers": "स्वयंसेवक", "volunteer": "स्वयंसेवक", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 7aa1d6ffc0..79d7436c39 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -439,7 +439,8 @@ "on": "En", "after": "Después de", "occurences": "ocurrencias", - "done": "Hecho" + "done": "Hecho", + "createChat": "Crear chat" }, "organizationActionItems": { "actionItemCategory": "Categoría de Acción", @@ -579,7 +580,8 @@ "on": "En", "after": "Después de", "occurences": "ocurrencias", - "done": "Hecho" + "done": "Hecho", + "createChat": "Crear chat" }, "funds": { "title": "Fondos", @@ -1084,6 +1086,18 @@ "loading": "Cargando...", "noAttendees": "No se encontraron asistentes" }, + + "eventRegistrant": { + "sort": "Ordenar", + "allRegistrants": "Todos los registrados", + "eventRegistrantsTable": "Tabla de registrados del evento", + "serialNumber": "Número de serie", + "registrant": "Registrado", + "registeredAt": "Registrado en", + "createdAt": "Creado en", + "addRegistrant": "Agregar registrado", + "noRegistrantsFound": "No se encontraron registrados" + }, "onSpotAttendee": { "title": "Asistente en el lugar", "enterFirstName": "Ingrese el nombre", @@ -1206,7 +1220,8 @@ "publicEvent": "Es público", "registerable": "Es registrable", "monthlyCalendarView": "Calendario mensual", - "yearlyCalendarView": "Calendario anual" + "yearlyCalendarView": "Calendario anual", + "createChat": "Crear chat" }, "userEventCard": { "location": "Ubicación", @@ -1250,14 +1265,22 @@ "createAdvertisement": "Crear publicidad" }, "userChat": { + "add": "Agregar", "chat": "Charlar", "search": "Buscar", "contacts": "Contactos", - "messages": "Mensajes" + "messages": "Mensajes", + "create": "crear", + "newChat": "nueva charla", + "newGroupChat": "Nuevo chat grupal", + "groupInfo": "Información del grupo", + "members": "Miembros", + "addMembers": "Add Members" }, "userChatRoom": { "selectContact": "Seleccione un contacto para iniciar una conversación", - "sendMessage": "Enviar mensaje" + "sendMessage": "Enviar mensaje", + "reply": "responder" }, "orgProfileField": { "loading": "Cargando..", @@ -1407,6 +1430,9 @@ "userPledges": { "title": "Mis Promesas" }, + "leaveOrganization": { + "title": "Dejar la organización" + }, "eventVolunteers": { "volunteers": "Voluntarios", "volunteer": "Voluntario", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index adafbdbe45..32a4f953be 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -439,7 +439,8 @@ "startDate": "开始日期", "endDate": "结束日期", "talawaApiUnavailable": "塔拉瓦 API 不可用", - "done": "完成" + "done": "完成", + "createChat": "创建聊天" }, "organizationActionItems": { "actionItemCategory": "行动项类别", @@ -579,7 +580,8 @@ "registerEvent": "注册活动", "close": "关闭", "talawaApiUnavailable": "塔拉瓦 API 不可用", - "done": "完成" + "done": "完成", + "createChat": "创建聊天" }, "funds": { "title": "基金", @@ -1082,6 +1084,17 @@ "loading": "加载中...", "noAttendees": "未找到参与者" }, + "eventRegistrant": { + "sort": "排序", + "allRegistrants": "所有注册者", + "eventRegistrantsTable": "活动注册者表", + "serialNumber": "序列号", + "registrant": "注册者", + "registeredAt": "注册时间", + "createdAt": "创建时间", + "addRegistrant": "添加注册者", + "noRegistrantsFound": "未找到注册者" + }, "onSpotAttendee": { "title": "现场参与者", "enterFirstName": "输入名字", @@ -1204,7 +1217,8 @@ "eventDescription": "活动描述", "eventLocation": "活动位置", "startDate": "开始日期", - "endDate": "结束日期" + "endDate": "结束日期", + "createChat": "创建聊天" }, "userEventCard": { "starts": "开始", @@ -1248,14 +1262,22 @@ "endOfResults": "结果结束" }, "userChat": { + "add": "添加", "chat": "聊天", "contacts": "联系方式", "search": "搜索", - "messages": "消息" + "messages": "消息", + "create": "创造", + "newChat": "新聊天", + "newGroupChat": "新群聊", + "groupInfo": "集团信息", + "members": "会员", + "addMembers": "Add Members" }, "userChatRoom": { "selectContact": "选择联系人开始对话", - "sendMessage": "发信息" + "sendMessage": "发信息", + "reply": "回复" }, "orgProfileField": { "loading": "加载中...", @@ -1405,6 +1427,9 @@ "userPledges": { "title": "我的承诺" }, + "leaveOrganization": { + "title": "离开组织" + }, "eventVolunteers": { "volunteers": "志愿者", "volunteer": "志愿者", diff --git a/scripts/githooks/check-localstorage-usage.js b/scripts/githooks/check-localstorage-usage.js old mode 100755 new mode 100644 index 0a811df307..0a7e2adbfc --- a/scripts/githooks/check-localstorage-usage.js +++ b/scripts/githooks/check-localstorage-usage.js @@ -86,10 +86,10 @@ if (filesWithLocalStorage.length > 0) { console.info( '\x1b[34m%s\x1b[0m', - '\nInfo: Consider using custom hook functions.' + '\nInfo: Consider using custom hook functions.', ); console.info( - 'Please use the getItem, setItem, and removeItem functions provided by the custom hook useLocalStorage.\n' + 'Please use the getItem, setItem, and removeItem functions provided by the custom hook useLocalStorage.\n', ); process.exit(1); diff --git a/src/App.test.tsx b/src/App.spec.tsx similarity index 70% rename from src/App.test.tsx rename to src/App.spec.tsx index f4fba2ebf8..45b73943cf 100644 --- a/src/App.test.tsx +++ b/src/App.spec.tsx @@ -1,25 +1,27 @@ -import React, { act } from 'react'; +import React from 'react'; import { render, screen } from '@testing-library/react'; import { Provider } from 'react-redux'; import { MockedProvider } from '@apollo/react-testing'; import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; -import 'jest-location-mock'; +import { describe, it, expect, vi } from 'vitest'; import App from './App'; import { store } from 'state/store'; import { CHECK_AUTH } from 'GraphQl/Queries/Queries'; import i18nForTest from './utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; -import useLocalStorage from 'utils/useLocalstorage'; -const { setItem } = useLocalStorage(); +vi.mock('@mui/x-charts/PieChart', () => ({ + pieArcLabelClasses: vi.fn(), + PieChart: vi.fn().mockImplementation(() => <>Test), + pieArcClasses: vi.fn(), +})); -// 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', () => ({ - pieArcLabelClasses: jest.fn(), - PieChart: jest.fn().mockImplementation(() => <>Test), - pieArcClasses: jest.fn(), +vi.mock('/src/assets/svgs/palisadoes.svg?react', () => ({ + default: () => Mocked SVG, +})); +vi.mock('/src/assets/svgs/talawa.svg?react', () => ({ + default: () => Mocked SVG, })); const MOCKS = [ @@ -59,16 +61,13 @@ const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink([], true); async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); + await new Promise((resolve) => { + setTimeout(resolve, ms); }); } describe('Testing the App Component', () => { - test('Component should be rendered properly and user is loggedin', async () => { - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + it('Component should be rendered properly and user is logged in', async () => { render( @@ -83,9 +82,9 @@ describe('Testing the App Component', () => { await wait(); - window.location.assign('/orglist'); + window.history.pushState({}, '', '/orglist'); await wait(); - expect(window.location).toBeAt('/orglist'); + expect(window.location.pathname).toBe('/orglist'); expect( screen.getByText( 'An open source application by Palisadoes Foundation volunteers', @@ -93,7 +92,7 @@ describe('Testing the App Component', () => { ).toBeTruthy(); }); - test('Component should be rendered properly and user is loggedout', async () => { + it('Component should be rendered properly and user is logged out', async () => { render( diff --git a/src/App.tsx b/src/App.tsx index 37f3bc301e..f78778bb1a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,7 +30,7 @@ import CommunityProfile from 'screens/CommunityProfile/CommunityProfile'; import OrganizationVenues from 'screens/OrganizationVenues/OrganizationVenues'; import Leaderboard from 'screens/Leaderboard/Leaderboard'; -import React, { useEffect } from 'react'; +import React from 'react'; // User Portal Components import Donate from 'screens/UserPortal/Donate/Donate'; import Events from 'screens/UserPortal/Events/Events'; @@ -39,19 +39,15 @@ import Organizations from 'screens/UserPortal/Organizations/Organizations'; import People from 'screens/UserPortal/People/People'; import Settings from 'screens/UserPortal/Settings/Settings'; import Chat from 'screens/UserPortal/Chat/Chat'; -import { useQuery } from '@apollo/client'; -import { CHECK_AUTH } from 'GraphQl/Queries/Queries'; import Advertisements from 'components/Advertisements/Advertisements'; import SecuredRouteForUser from 'components/UserPortal/SecuredRouteForUser/SecuredRouteForUser'; - -import useLocalStorage from 'utils/useLocalstorage'; import UserScreen from 'screens/UserPortal/UserScreen/UserScreen'; import EventDashboardScreen from 'components/EventDashboardScreen/EventDashboardScreen'; import Campaigns from 'screens/UserPortal/Campaigns/Campaigns'; import Pledges from 'screens/UserPortal/Pledges/Pledges'; import VolunteerManagement from 'screens/UserPortal/Volunteer/VolunteerManagement'; -const { setItem } = useLocalStorage(); +// const { setItem } = useLocalStorage(); /** * This is the main function for our application. It sets up all the routes and components, @@ -96,20 +92,20 @@ function app(): JSX.Element { // TODO: Fetch Installed plugin extras and store for use within MainContent and Side Panel Components. - const { data, loading } = useQuery(CHECK_AUTH); + // const { data, loading } = useQuery(CHECK_AUTH); - useEffect(() => { - if (data) { - setItem('name', `${data.checkAuth.firstName} ${data.checkAuth.lastName}`); - setItem('id', data.checkAuth._id); - setItem('email', data.checkAuth.email); - setItem('IsLoggedIn', 'TRUE'); - setItem('FirstName', data.checkAuth.firstName); - setItem('LastName', data.checkAuth.lastName); - setItem('UserImage', data.checkAuth.image); - setItem('Email', data.checkAuth.email); - } - }, [data, loading]); + // useEffect(() => { + // if (data) { + // setItem('name', `${data.checkAuth.firstName} ${data.checkAuth.lastName}`); + // setItem('id', data.checkAuth._id); + // setItem('email', data.checkAuth.email); + // setItem('IsLoggedIn', 'TRUE'); + // setItem('FirstName', data.checkAuth.firstName); + // setItem('LastName', data.checkAuth.lastName); + // setItem('UserImage', data.checkAuth.image); + // setItem('Email', data.checkAuth.email); + // } + // }, [data, loading]); const extraRoutes = Object.entries(installedPlugins).map( ( @@ -189,13 +185,14 @@ function app(): JSX.Element { }> } /> } /> - } /> + {/* } /> */} }> } /> } /> } /> } /> } /> + } /> } /> } /> - {/* */} } /> diff --git a/src/GraphQl/Mutations/ActionItemCategoryMutations.ts b/src/GraphQl/Mutations/ActionItemCategoryMutations.ts index 92e7b0968c..4b4c51bc78 100644 --- a/src/GraphQl/Mutations/ActionItemCategoryMutations.ts +++ b/src/GraphQl/Mutations/ActionItemCategoryMutations.ts @@ -29,7 +29,7 @@ export const CREATE_ACTION_ITEM_CATEGORY_MUTATION = gql` * * @param id - The id of the ActionItemCategory to be updated. * @param name - Updated name of the ActionItemCategory. - * @param isDisabled - Updated disabled status of the ActionItemCategory. + * @param isDisabled - Updated disabled status of the ActionItemCategory. */ export const UPDATE_ACTION_ITEM_CATEGORY_MUTATION = gql` diff --git a/src/GraphQl/Mutations/OrganizationMutations.ts b/src/GraphQl/Mutations/OrganizationMutations.ts index 68a0c9026c..152668ab1e 100644 --- a/src/GraphQl/Mutations/OrganizationMutations.ts +++ b/src/GraphQl/Mutations/OrganizationMutations.ts @@ -63,6 +63,7 @@ export const CREATE_CHAT = gql` $organizationId: ID $isGroup: Boolean! $name: String + $image: String ) { createChat( data: { @@ -70,6 +71,7 @@ export const CREATE_CHAT = gql` organizationId: $organizationId isGroup: $isGroup name: $name + image: $image } ) { _id @@ -77,20 +79,67 @@ export const CREATE_CHAT = gql` } `; +export const ADD_USER_TO_GROUP_CHAT = gql` + mutation addUserToGroupChat($userId: ID!, $chatId: ID!) { + addUserToGroupChat(userId: $userId, chatId: $chatId) { + _id + } + } +`; + +export const MARK_CHAT_MESSAGES_AS_READ = gql` + mutation markChatMessagesAsRead($chatId: ID!, $userId: ID!) { + markChatMessagesAsRead(chatId: $chatId, userId: $userId) { + _id + } + } +`; + +export const UPDATE_CHAT = gql` + mutation updateChat($input: UpdateChatInput!) { + updateChat(input: $input) { + _id + } + } +`; + +export const EDIT_CHAT_MESSAGE = gql` + mutation updateChatMessage( + $messageId: ID! + $messageContent: String! + $chatId: ID! + ) { + updateChatMessage( + input: { + messageId: $messageId + messageContent: $messageContent + chatId: $chatId + } + ) { + _id + messageContent + updatedAt + } + } +`; + export const SEND_MESSAGE_TO_CHAT = gql` mutation sendMessageToChat( $chatId: ID! $replyTo: ID - $messageContent: String! + $media: String + $messageContent: String ) { sendMessageToChat( chatId: $chatId replyTo: $replyTo messageContent: $messageContent + media: $media ) { _id createdAt messageContent + media replyTo { _id createdAt diff --git a/src/GraphQl/Mutations/mutations.ts b/src/GraphQl/Mutations/mutations.ts index 628328987e..79b8c1cfd1 100644 --- a/src/GraphQl/Mutations/mutations.ts +++ b/src/GraphQl/Mutations/mutations.ts @@ -283,6 +283,7 @@ export const CREATE_EVENT_MUTATION = gql` $count: PositiveInt $interval: PositiveInt $weekDayOccurenceInMonth: Int + $createChat: Boolean! ) { createEvent( data: { @@ -298,6 +299,7 @@ export const CREATE_EVENT_MUTATION = gql` startTime: $startTime endTime: $endTime location: $location + createChat: $createChat } recurrenceRuleData: { recurrenceStartDate: $recurrenceStartDate diff --git a/src/GraphQl/Queries/PlugInQueries.ts b/src/GraphQl/Queries/PlugInQueries.ts index 508da522e9..346fb712f3 100644 --- a/src/GraphQl/Queries/PlugInQueries.ts +++ b/src/GraphQl/Queries/PlugInQueries.ts @@ -167,6 +167,7 @@ export const CHAT_BY_ID = gql` _id createdAt messageContent + media replyTo { _id createdAt @@ -192,18 +193,73 @@ export const CHAT_BY_ID = gql` firstName lastName email + image + } + admins { + _id + firstName + lastName + email + image } + unseenMessagesByUsers } } `; -export const CHATS_LIST = gql` - query ChatsByUserId($id: ID!) { - chatsByUserId(id: $id) { +export const GROUP_CHAT_LIST = gql` + query groupChatsByUserId { + getGroupChatsByUserId { _id isGroup name + creator { + _id + firstName + lastName + email + } + messages { + _id + createdAt + messageContent + media + sender { + _id + firstName + lastName + email + } + } + organization { + _id + name + } + users { + _id + firstName + lastName + email + image + } + admins { + _id + firstName + lastName + email + image + } + unseenMessagesByUsers + } + } +`; +export const UNREAD_CHAT_LIST = gql` + query unreadChatList { + getUnreadChatsByUserId { + _id + isGroup + name creator { _id firstName @@ -214,6 +270,7 @@ export const CHATS_LIST = gql` _id createdAt messageContent + media sender { _id firstName @@ -232,10 +289,73 @@ export const CHATS_LIST = gql` email image } + admins { + _id + firstName + lastName + email + image + } + unseenMessagesByUsers } } `; +export const CHATS_LIST = gql` + query ChatsByUserId($id: ID!, $searchString: String) { + chatsByUserId( + id: $id + where: { + name_contains: $searchString + user: { + firstName_contains: $searchString + lastName_contains: $searchString + } + } + ) { + _id + isGroup + name + image + creator { + _id + firstName + lastName + email + } + messages { + _id + createdAt + messageContent + sender { + _id + firstName + lastName + email + } + } + organization { + _id + name + } + users { + _id + firstName + lastName + email + image + } + admins { + _id + firstName + lastName + email + image + } + unseenMessagesByUsers + } + } +`; /** * GraphQL query to check if an organization is a sample organization. * diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 81442cbad2..9cf1a92755 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -328,6 +328,16 @@ export const EVENT_ATTENDEES = gql` } `; +export const EVENT_REGISTRANTS = gql` + query GetEventAttendeesByEventId($eventId: ID!) { + getEventAttendeesByEventId(eventId: $eventId) { + userId + isRegistered + _id + } + } +`; + export const EVENT_CHECKINS = gql` query eventCheckIns($id: ID!) { event(id: $id) { diff --git a/src/assets/svgs/agenda-items.svg b/src/assets/svgs/agenda-items.svg index 343d1808b4..b38e571a5a 100644 --- a/src/assets/svgs/agenda-items.svg +++ b/src/assets/svgs/agenda-items.svg @@ -1 +1 @@ -notebook +notebook diff --git a/src/components/AddOn/core/AddOnEntry/AddOnEntry.module.css b/src/components/AddOn/core/AddOnEntry/AddOnEntry.module.css deleted file mode 100644 index c5dd86c8d4..0000000000 --- a/src/components/AddOn/core/AddOnEntry/AddOnEntry.module.css +++ /dev/null @@ -1,24 +0,0 @@ -.entrytoggle { - margin: 24px 24px 0 auto; - width: fit-content; -} - -.entryaction { - margin-left: auto; - display: flex !important; - align-items: center; - background-color: transparent; - color: #31bb6b; -} -.card { - border: 4px solid green; -} -.entryaction i { - margin-right: 8px; -} - -.entryaction .spinner-grow { - height: 1rem; - width: 1rem; - margin-right: 8px; -} diff --git a/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx b/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx deleted file mode 100644 index 3d800eb59f..0000000000 --- a/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import AddOnEntry from './AddOnEntry'; -import { - ApolloClient, - ApolloProvider, - InMemoryCache, - ApolloLink, - HttpLink, -} from '@apollo/client'; - -import type { NormalizedCacheObject } from '@apollo/client'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; -import { BACKEND_URL } from 'Constant/constant'; -import i18nForTest from 'utils/i18nForTest'; -import { I18nextProvider } from 'react-i18next'; -import userEvent from '@testing-library/user-event'; -import { MockedProvider, wait } from '@apollo/react-testing'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import { ADD_ON_ENTRY_MOCK } from './AddOnEntryMocks'; -import { ToastContainer } from 'react-toastify'; -import useLocalStorage from 'utils/useLocalstorage'; - -const { getItem } = useLocalStorage(); - -const link = new StaticMockLink(ADD_ON_ENTRY_MOCK, true); - -const httpLink = new HttpLink({ - uri: BACKEND_URL, - headers: { - authorization: 'Bearer ' + getItem('token') || '', - }, -}); -console.error = jest.fn(); -const client: ApolloClient = new ApolloClient({ - cache: new InMemoryCache(), - link: ApolloLink.from([httpLink]), -}); -let mockID: string | undefined = '1'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockID }), -})); - -describe('Testing AddOnEntry', () => { - const props = { - id: 'string', - enabled: true, - title: 'string', - description: 'string', - createdBy: 'string', - component: 'string', - installed: true, - configurable: true, - modified: true, - isInstalled: true, - getInstalledPlugins: (): { sample: string } => { - return { sample: 'sample' }; - }, - }; - - test('should render modal and take info to add plugin for registered organization', () => { - const { getByTestId } = render( - - - - - {} - - - - , - ); - expect(getByTestId('AddOnEntry')).toBeInTheDocument(); - }); - - test('uses default values for title and description when not provided', () => { - // Render the component with only required parameters - const mockGetInstalledPlugins = jest.fn(); - render( - - - - - - - - - , - ); - - const titleElement = screen.getByText('No title provided'); // This will check for the default empty string in the title - const descriptionElement = screen.getByText('Description not available'); // This will check for the default empty string in the description - expect(titleElement).toBeInTheDocument(); // Ensure the title element with default value exists - expect(descriptionElement).toBeInTheDocument(); // Ensure the description element with default value exists - }); - - it('renders correctly', () => { - const props = { - id: '1', - title: 'Test Addon', - description: 'Test addon description', - createdBy: 'Test User', - component: 'string', - installed: true, - configurable: true, - modified: true, - isInstalled: true, - uninstalledOrgs: [], - enabled: true, - getInstalledPlugins: (): { sample: string } => { - return { sample: 'sample' }; - }, - }; - - const { getByText } = render( - - - - - {} - - - - , - ); - - expect(getByText('Test Addon')).toBeInTheDocument(); - expect(getByText('Test addon description')).toBeInTheDocument(); - expect(getByText('Test User')).toBeInTheDocument(); - }); - - it('Uninstall Button works correctly', async () => { - const props = { - id: '1', - title: 'Test Addon', - description: 'Test addon description', - createdBy: 'Test User', - component: 'string', - installed: true, - configurable: true, - modified: true, - isInstalled: true, - uninstalledOrgs: [], - enabled: true, - getInstalledPlugins: (): { sample: string } => { - return { sample: 'sample' }; - }, - }; - mockID = 'undefined'; - const { findByText, getByTestId } = render( - - - - - - {} - - - - , - ); - await wait(100); - const btn = getByTestId('AddOnEntry_btn_install'); - await userEvent.click(btn); - await wait(100); - expect(btn.innerHTML).toMatch(/Install/i); - expect( - await findByText('This feature is now removed from your organization'), - ).toBeInTheDocument(); - await userEvent.click(btn); - await wait(100); - - expect(btn.innerHTML).toMatch(/Uninstall/i); - expect( - await findByText('This feature is now enabled in your organization'), - ).toBeInTheDocument(); - }); - - it('Check if uninstalled orgs includes current org', async () => { - const props = { - id: '1', - title: 'Test Addon', - description: 'Test addon description', - createdBy: 'Test User', - component: 'string', - installed: true, - configurable: true, - modified: true, - isInstalled: true, - uninstalledOrgs: ['undefined'], - enabled: true, - getInstalledPlugins: (): { sample: string } => { - return { sample: 'sample' }; - }, - }; - - const { getByTestId } = render( - - - - - {} - - - - , - ); - await wait(100); - const btn = getByTestId('AddOnEntry_btn_install'); - expect(btn.innerHTML).toMatch(/install/i); - }); - test('should be redirected to /orglist if orgId is undefined', async () => { - mockID = undefined; - render( - - - - - {} - - - - , - ); - await wait(100); - expect(window.location.pathname).toEqual('/orglist'); - }); -}); diff --git a/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx b/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx index 12805568f6..e2971fee14 100644 --- a/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx +++ b/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import styles from './AddOnEntry.module.css'; +import styles from './../../../../style/app.module.css'; import { Button, Card, Spinner } from 'react-bootstrap'; import { UPDATE_INSTALL_STATUS_PLUGIN_MUTATION } from 'GraphQl/Mutations/mutations'; import { useMutation } from '@apollo/client'; diff --git a/src/components/AddOn/core/AddOnRegister/AddOnRegister.test.tsx b/src/components/AddOn/core/AddOnRegister/AddOnRegister.spec.tsx similarity index 89% rename from src/components/AddOn/core/AddOnRegister/AddOnRegister.test.tsx rename to src/components/AddOn/core/AddOnRegister/AddOnRegister.spec.tsx index dc6a7c2091..bbddef4fc7 100644 --- a/src/components/AddOn/core/AddOnRegister/AddOnRegister.test.tsx +++ b/src/components/AddOn/core/AddOnRegister/AddOnRegister.spec.tsx @@ -20,6 +20,7 @@ import i18nForTest from 'utils/i18nForTest'; import { I18nextProvider } from 'react-i18next'; import { toast } from 'react-toastify'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; const { getItem } = useLocalStorage(); @@ -74,26 +75,30 @@ const pluginData = { pluginDesc: 'Test Description', }; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), + success: vi.fn(), }, })); -const mockNavigate = jest.fn(); +const mockNavigate = vi.fn(); let mockId: string | undefined = 'id'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockId }), - useNavigate: () => mockNavigate, -})); + +vi.mock('react-router-dom', async () => { + const actual = await import('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: mockId }), + useNavigate: () => mockNavigate, + }; +}); describe('Testing AddOnRegister', () => { const props = { id: '6234d8bf6ud937ddk70ecc5c9', }; - test('should render modal and take info to add plugin for registered organization', async () => { + it('should render modal and take info to add plugin for registered organization', async () => { await act(async () => { render( @@ -127,7 +132,7 @@ describe('Testing AddOnRegister', () => { ); }); - test('Expect toast.success to be called on successful plugin addition', async () => { + it('Expect toast.success to be called on successful plugin addition', async () => { await act(async () => { render( @@ -158,7 +163,7 @@ describe('Testing AddOnRegister', () => { expect(toast.success).toHaveBeenCalledWith('Plugin added Successfully'); }); - test('Expect the window to reload after successful plugin addition', async () => { + it('Expect the window to reload after successful plugin addition', async () => { await act(async () => { render( @@ -189,7 +194,7 @@ describe('Testing AddOnRegister', () => { expect(mockNavigate).toHaveBeenCalledWith(0); }); - test('should be redirected to /orglist if orgId is undefined', async () => { + it('should be redirected to /orglist if orgId is undefined', async () => { mockId = undefined; render( diff --git a/src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx b/src/components/AddOn/core/AddOnStore/AddOnStore.spec.tsx similarity index 88% rename from src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx rename to src/components/AddOn/core/AddOnStore/AddOnStore.spec.tsx index abb4a80ce8..9a7eb08e1b 100644 --- a/src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx +++ b/src/components/AddOn/core/AddOnStore/AddOnStore.spec.tsx @@ -1,5 +1,4 @@ import React, { act } from 'react'; -import 'jest-location-mock'; import { fireEvent, render, screen } from '@testing-library/react'; import { ApolloClient, @@ -20,6 +19,7 @@ import { ORGANIZATIONS_LIST, PLUGIN_GET } from 'GraphQl/Queries/Queries'; import userEvent from '@testing-library/user-event'; import useLocalStorage from 'utils/useLocalstorage'; import { MockedProvider } from '@apollo/react-testing'; +import { vi, describe, test, expect } from 'vitest'; const { getItem } = useLocalStorage(); interface InterfacePlugin { @@ -27,10 +27,10 @@ interface InterfacePlugin { pluginName: string; component: string; } -jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ +vi.mock('components/AddOn/support/services/Plugin.helper', () => ({ __esModule: true, - default: jest.fn().mockImplementation(() => ({ - fetchStore: jest.fn().mockResolvedValue([ + default: vi.fn().mockImplementation(() => ({ + fetchStore: vi.fn().mockResolvedValue([ { _id: '1', pluginName: 'Plugin 1', @@ -47,7 +47,7 @@ jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ }, // Add more mock data as needed ]), - fetchInstalled: jest.fn().mockResolvedValue([ + fetchInstalled: vi.fn().mockResolvedValue([ { _id: '1', pluginName: 'Installed Plugin 1', @@ -64,18 +64,16 @@ jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ }, // Add more mock data as needed ]), - generateLinks: jest - .fn() - .mockImplementation((plugins: InterfacePlugin[]) => { - return plugins - .filter((plugin) => plugin.enabled) - .map((installedPlugin) => { - return { - name: installedPlugin.pluginName, - url: `/plugin/${installedPlugin.component.toLowerCase()}`, - }; - }); - }), + generateLinks: vi.fn().mockImplementation((plugins: InterfacePlugin[]) => { + return plugins + .filter((plugin) => plugin.enabled) + .map((installedPlugin) => { + return { + name: installedPlugin.pluginName, + url: `/plugin/${installedPlugin.component.toLowerCase()}`, + }; + }); + }), })), })); @@ -99,11 +97,11 @@ const client: ApolloClient = new ApolloClient({ link: ApolloLink.from([httpLink]), }); -jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ +vi.mock('components/AddOn/support/services/Plugin.helper', () => ({ __esModule: true, - default: jest.fn().mockImplementation(() => ({ - fetchInstalled: jest.fn().mockResolvedValue([]), - fetchStore: jest.fn().mockResolvedValue([]), + default: vi.fn().mockImplementation(() => ({ + fetchInstalled: vi.fn().mockResolvedValue([]), + fetchStore: vi.fn().mockResolvedValue([]), })), })); @@ -168,10 +166,15 @@ const PLUGIN_LOADING_MOCK = { loading: true, }, }; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'undefined' }), -})); + +vi.mock('react-router-dom', async () => { + const actualModule = await vi.importActual('react-router-dom'); + return { + ...actualModule, + useParams: () => ({ orgId: 'undefined' }), + }; +}); + const ORGANIZATIONS_LIST_MOCK = { request: { query: ORGANIZATIONS_LIST, diff --git a/src/components/AddOn/support/components/Action/Action.test.tsx b/src/components/AddOn/support/components/Action/Action.test.tsx deleted file mode 100644 index ce6cd633b9..0000000000 --- a/src/components/AddOn/support/components/Action/Action.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { Provider } from 'react-redux'; - -import { store } from 'state/store'; -import Action from './Action'; - -describe('Testing Action Component', () => { - const props = { - children: 'dummy children', - label: 'dummy label', - }; - - test('should render props and text elements test for the page component', () => { - const { getByText } = render( - - - , - ); - - expect(getByText(props.label)).toBeInTheDocument(); - expect(getByText(props.children)).toBeInTheDocument(); - }); -}); diff --git a/src/components/AddOn/support/components/MainContent/MainContent.test.tsx b/src/components/AddOn/support/components/MainContent/MainContent.spec.tsx similarity index 70% rename from src/components/AddOn/support/components/MainContent/MainContent.test.tsx rename to src/components/AddOn/support/components/MainContent/MainContent.spec.tsx index 81adbc916e..90988e8d2b 100644 --- a/src/components/AddOn/support/components/MainContent/MainContent.test.tsx +++ b/src/components/AddOn/support/components/MainContent/MainContent.spec.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -11,7 +12,7 @@ describe('Testing MainContent component', () => { children: 'This is a dummy text', }; - test('should render props and children for the Main Content', () => { + it('should render props and children for the Main Content', () => { const { getByTestId, getByText } = render( @@ -20,7 +21,7 @@ describe('Testing MainContent component', () => { , ); - expect(getByTestId('mainContentCheck')).toBeInTheDocument(); - expect(getByText(props.children)).toBeInTheDocument(); + expect(getByTestId('mainContentCheck')).not.toBeNull(); + expect(getByText(props.children)).not.toBeNull(); }); }); diff --git a/src/components/AddOn/support/components/SidePanel/SidePanel.test.tsx b/src/components/AddOn/support/components/SidePanel/SidePanel.spec.tsx similarity index 82% rename from src/components/AddOn/support/components/SidePanel/SidePanel.test.tsx rename to src/components/AddOn/support/components/SidePanel/SidePanel.spec.tsx index d929278d0e..4a5f4e5692 100644 --- a/src/components/AddOn/support/components/SidePanel/SidePanel.test.tsx +++ b/src/components/AddOn/support/components/SidePanel/SidePanel.spec.tsx @@ -11,11 +11,16 @@ const client: ApolloClient = new ApolloClient({ }); describe('Testing Contribution Stats', () => { + /** + * Props to be passed to the `SidePanel` component during the test. + */ const props = { collapse: true, children: '234', }; - + /** + * Verifies that the `SidePanel` component renders correctly with given props. + */ test('should render props and text elements test for the SidePanel component', () => { render( diff --git a/src/components/AddOn/support/services/Plugin.helper.test.ts b/src/components/AddOn/support/services/Plugin.helper.spec.ts similarity index 65% rename from src/components/AddOn/support/services/Plugin.helper.test.ts rename to src/components/AddOn/support/services/Plugin.helper.spec.ts index 39f0a5d12c..51c8ec4bc5 100644 --- a/src/components/AddOn/support/services/Plugin.helper.test.ts +++ b/src/components/AddOn/support/services/Plugin.helper.spec.ts @@ -1,14 +1,27 @@ import PluginHelper from './Plugin.helper'; +import { vi } from 'vitest'; + +/** + * This file contains unit tests for the PluginHelper component. + * + * The tests cover: + * - Verification that the class contains the required method definitions. + * - Correct functionality of the `generateLinks` method, including returning proper objects. + * - Proper behavior of the `fetchStore` method, including handling of mocked JSON responses. + * - Functionality of the `fetchInstalled` method, verifying it returns the expected JSON data. + * + * These tests use Vitest for test execution and mock the global `fetch` function for asynchronous tests. + */ describe('Testing src/components/AddOn/support/services/Plugin.helper.ts', () => { - test('Class should contain the required method definitions', () => { + it('Class should contain the required method definitions', () => { const pluginHelper = new PluginHelper(); expect(pluginHelper).toHaveProperty('fetchStore'); expect(pluginHelper).toHaveProperty('fetchInstalled'); expect(pluginHelper).toHaveProperty('generateLinks'); expect(pluginHelper).toHaveProperty('generateLinks'); }); - test('generateLinks should return proper objects', () => { + it('generateLinks should return proper objects', () => { const obj = { enabled: true, name: 'demo', @@ -28,9 +41,9 @@ describe('Testing src/components/AddOn/support/services/Plugin.helper.ts', () => }); it('fetchStore should return expected JSON', async () => { const helper = new PluginHelper(); - const spy = jest.spyOn(global, 'fetch').mockImplementation(() => { + const spy = vi.spyOn(global, 'fetch').mockImplementation(() => { const response = new Response(); - response.json = jest + response.json = vi .fn() .mockReturnValue(Promise.resolve({ data: 'mock data' })); return Promise.resolve(response); @@ -46,11 +59,11 @@ describe('Testing src/components/AddOn/support/services/Plugin.helper.ts', () => { name: 'plugin1', component: 'Component1', enabled: true }, { name: 'plugin2', component: 'Component2', enabled: false }, ]; - jest.spyOn(global, 'fetch').mockImplementation(() => { + vi.spyOn(global, 'fetch').mockImplementation(() => { const response = new Response(); - response.json = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + response.json = vi.fn().mockReturnValue(Promise.resolve(mockResponse)); return Promise.resolve(response); - }) as jest.Mock; + }); const result = await pluginHelper.fetchInstalled(); expect(result).toEqual(mockResponse); }); diff --git a/src/components/Advertisements/Advertisements.test.tsx b/src/components/Advertisements/Advertisements.spec.tsx similarity index 96% rename from src/components/Advertisements/Advertisements.test.tsx rename to src/components/Advertisements/Advertisements.spec.tsx index 88bbb1255c..1e2d92b392 100644 --- a/src/components/Advertisements/Advertisements.test.tsx +++ b/src/components/Advertisements/Advertisements.spec.tsx @@ -1,4 +1,6 @@ import React, { act } from 'react'; +import { describe, test, expect, vi } from 'vitest'; + import { ApolloClient, ApolloLink, @@ -7,25 +9,28 @@ import { InMemoryCache, } from '@apollo/client'; import { MockedProvider } from '@apollo/client/testing'; + import { fireEvent, render, screen } from '@testing-library/react'; -import 'jest-location-mock'; import type { DocumentNode, NormalizedCacheObject } from '@apollo/client'; import userEvent from '@testing-library/user-event'; import { BACKEND_URL } from 'Constant/constant'; -import { ADD_ADVERTISEMENT_MUTATION } from 'GraphQl/Mutations/mutations'; + +import { ADD_ADVERTISEMENT_MUTATION } from '../../GraphQl/Mutations/mutations'; import { ORGANIZATIONS_LIST, ORGANIZATION_ADVERTISEMENT_LIST, PLUGIN_GET, -} from 'GraphQl/Queries/Queries'; +} from '../../GraphQl/Queries/Queries'; + import { I18nextProvider } from 'react-i18next'; + import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; -import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; -import useLocalStorage from 'utils/useLocalstorage'; +import { store } from '../../state/store'; +import i18nForTest from '../../utils/i18nForTest'; +import useLocalStorage from '../../utils/useLocalstorage'; import Advertisement from './Advertisements'; const { getItem } = useLocalStorage(); @@ -50,18 +55,22 @@ const client: ApolloClient = new ApolloClient({ link: ApolloLink.from([httpLink]), }); -jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ +vi.mock('components/AddOn/support/services/Plugin.helper', () => ({ __esModule: true, - default: jest.fn().mockImplementation(() => ({ - fetchInstalled: jest.fn().mockResolvedValue([]), - fetchStore: jest.fn().mockResolvedValue([]), + default: vi.fn().mockImplementation(() => ({ + fetchInstalled: vi.fn().mockResolvedValue([]), + fetchStore: vi.fn().mockResolvedValue([]), })), })); let mockID: string | undefined = '1'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockID }), -})); + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: mockID }), + }; +}); const today = new Date(); const tomorrow = today; @@ -466,18 +475,16 @@ describe('Testing Advertisement Component', () => { /\b(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})\s+(\d{4})\b/, ); let dateObject = new Date(); - if (dateMatch) { const monthName = dateMatch[1]; const day = parseInt(dateMatch[2], 10); const year = parseInt(dateMatch[3], 10); - const monthIndex = 'JanFebMarAprMayJunJulAugSepOctNovDec'.indexOf(monthName) / 3; dateObject = new Date(year, monthIndex, day); } - + console.log(dateObject); expect(dateObject.getTime()).toBeLessThan(new Date().getTime()); }); diff --git a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.spec.tsx similarity index 90% rename from src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx rename to src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.spec.tsx index dbd6f88cc3..4d27df6e22 100644 --- a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx +++ b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.spec.tsx @@ -21,6 +21,8 @@ import useLocalStorage from 'utils/useLocalstorage'; import { MockedProvider } from '@apollo/client/testing'; import { ORGANIZATION_ADVERTISEMENT_LIST } from 'GraphQl/Queries/OrganizationQueries'; import { DELETE_ADVERTISEMENT_BY_ID } from 'GraphQl/Mutations/mutations'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import '@testing-library/jest-dom'; const { getItem } = useLocalStorage(); @@ -30,7 +32,6 @@ const httpLink = new HttpLink({ authorization: 'Bearer ' + getItem('token') || '', }, }); - const translations = JSON.parse( JSON.stringify( i18nForTest.getDataByLanguage('en')?.translation?.advertisement ?? null, @@ -42,22 +43,30 @@ const client: ApolloClient = new ApolloClient({ link: ApolloLink.from([httpLink]), }); -const mockUseMutation = jest.fn(); -jest.mock('@apollo/client', () => { - const originalModule = jest.requireActual('@apollo/client'); +const mockUseMutation = vi.fn(); +vi.mock('@apollo/client', async () => { + const actual = await vi.importActual('@apollo/client'); return { - ...originalModule, + ...actual, useMutation: () => mockUseMutation(), }; }); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '1' }), -})); + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: '1' }), + }; +}); describe('Testing Advertisement Entry Component', () => { - test('Testing rendering and deleting of advertisement', async () => { - const deleteAdByIdMock = jest.fn(); + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('Testing rendering and deleting of advertisement', async () => { + const deleteAdByIdMock = vi.fn(); mockUseMutation.mockReturnValue([deleteAdByIdMock]); const { getByTestId, getAllByText } = render( @@ -73,7 +82,7 @@ describe('Testing Advertisement Entry Component', () => { name="Advert1" organizationId="1" type="POPUP" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -130,13 +139,13 @@ describe('Testing Advertisement Entry Component', () => { expect(deletionFailedText).toBeNull(); }); }); + it('should use default props when none are provided', () => { render( , - ): void { + setAfter={function () // _value: React.SetStateAction, + : void { throw new Error('Function not implemented.'); }} />, @@ -156,9 +165,9 @@ describe('Testing Advertisement Entry Component', () => { // Check that the component renders with default `startDate` const defaultStartDate = new Date().toDateString(); - console.log(screen.getByText); expect(screen.getByText(`Ends on ${defaultStartDate}`)).toBeInTheDocument(); //fix text "Ends on"? }); + it('should correctly override default props when values are provided', () => { const mockName = 'Test Ad'; const mockType = 'Banner'; @@ -176,9 +185,8 @@ describe('Testing Advertisement Entry Component', () => { startDate={mockStartDate} organizationId={mockOrganizationId} id={''} - setAfter={function ( - _value: React.SetStateAction, - ): void { + setAfter={function () // _value: React.SetStateAction, + : void { throw new Error('Function not implemented.'); }} />, @@ -186,8 +194,13 @@ describe('Testing Advertisement Entry Component', () => { // Check that the component renders with provided values expect(getByText(mockName)).toBeInTheDocument(); - // Add more checks based on how each prop affects rendering + expect(getByText(mockType)).toBeInTheDocument(); + expect(screen.getByTestId('media')).toHaveAttribute('src', mockMediaUrl); + expect( + getByText(`Ends on ${mockEndDate.toDateString()}`), + ).toBeInTheDocument(); }); + it('should open and close the dropdown when options button is clicked', () => { const { getByTestId, queryByText, getAllByText } = render( @@ -203,7 +216,7 @@ describe('Testing Advertisement Entry Component', () => { name="Advert1" organizationId="1" type="POPUP" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -235,8 +248,8 @@ describe('Testing Advertisement Entry Component', () => { expect(queryByText('Edit')).toBeNull(); }); - test('Updates the advertisement and shows success toast on successful update', async () => { - const updateAdByIdMock = jest.fn().mockResolvedValue({ + it('Updates the advertisement and shows success toast on successful update', async () => { + const updateAdByIdMock = vi.fn().mockResolvedValue({ data: { updateAdvertisement: { advertisement: { @@ -266,7 +279,7 @@ describe('Testing Advertisement Entry Component', () => { organizationId="1" mediaUrl="" id="1" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -312,8 +325,8 @@ describe('Testing Advertisement Entry Component', () => { }); }); - test('Simulating if the mutation doesnt have data variable while updating', async () => { - const updateAdByIdMock = jest.fn().mockResolvedValue({ + it('Simulating if the mutation doesnt have data variable while updating', async () => { + const updateAdByIdMock = vi.fn().mockResolvedValue({ updateAdvertisement: { _id: '1', name: 'Updated Advertisement', @@ -336,7 +349,7 @@ describe('Testing Advertisement Entry Component', () => { organizationId="1" mediaUrl="" id="1" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -372,15 +385,13 @@ describe('Testing Advertisement Entry Component', () => { }); }); - test('Simulating if the mutation does not have data variable while registering', async () => { - Object.defineProperty(window, 'location', { - configurable: true, - value: { - reload: jest.fn(), - href: 'https://example.com/page/id=1', - }, + it('Simulating if the mutation does not have data variable while registering', async () => { + vi.stubGlobal('location', { + reload: vi.fn(), + href: 'https://example.com/page/id=1', }); - const createAdByIdMock = jest.fn().mockResolvedValue({ + + const createAdByIdMock = vi.fn().mockResolvedValue({ data1: { createAdvertisement: { _id: '1', @@ -397,7 +408,7 @@ describe('Testing Advertisement Entry Component', () => { { } @@ -449,8 +460,9 @@ describe('Testing Advertisement Entry Component', () => { }, }); }); - test('delet advertisement', async () => { - const deleteAdByIdMock = jest.fn(); + + it('delete advertisement', async () => { + const deleteAdByIdMock = vi.fn(); const mocks = [ { request: { @@ -576,7 +588,7 @@ describe('Testing Advertisement Entry Component', () => { name="Advert1" organizationId="1" type="POPUP" - setAfter={jest.fn()} + setAfter={vi.fn()} /> diff --git a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx similarity index 87% rename from src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx rename to src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx index 0646a94819..80ef45226f 100644 --- a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx +++ b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx @@ -25,14 +25,24 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import userEvent from '@testing-library/user-event'; import useLocalStorage from 'utils/useLocalstorage'; import { ORGANIZATION_ADVERTISEMENT_LIST } from 'GraphQl/Queries/Queries'; +import { vi } from 'vitest'; const { getItem } = useLocalStorage(); -jest.mock('react-toastify', () => ({ +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: '1' }), + useNavigate: vi.fn(), + }; +}); + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); @@ -120,11 +130,22 @@ const httpLink = new HttpLink({ }, }); +vi.mock('utils/useLocalstorage', () => ({ + default: () => ({ + getItem: vi.fn().mockReturnValue('token'), + }), +})); + const client: ApolloClient = new ApolloClient({ cache: new InMemoryCache(), link: ApolloLink.from([httpLink]), }); +// const client: ApolloClient = new ApolloClient({ +// cache: new InMemoryCache(), +// link, +// }); + const translations = { ...JSON.parse( JSON.stringify( @@ -135,10 +156,6 @@ const translations = { ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), }; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '1' }), -})); describe('Testing Advertisement Register Component', () => { test('AdvertismentRegister component loads correctly in register mode', async () => { const { getByText } = render( @@ -153,7 +170,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -166,8 +183,7 @@ describe('Testing Advertisement Register Component', () => { }); test('create advertisement', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); await act(async () => { render( @@ -182,7 +198,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -254,12 +270,11 @@ describe('Testing Advertisement Register Component', () => { ); expect(setTimeoutSpy).toHaveBeenCalled(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('update advertisement', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); await act(async () => { render( @@ -274,7 +289,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} formStatus="edit" /> @@ -346,13 +361,12 @@ describe('Testing Advertisement Register Component', () => { expect(setTimeoutSpy).toHaveBeenCalled(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Logs error to the console and shows error toast when advertisement creation fails', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); - const toastErrorSpy = jest.spyOn(toast, 'error'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); + const toastErrorSpy = vi.spyOn(toast, 'error'); await act(async () => { render( @@ -367,7 +381,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -397,12 +411,11 @@ describe('Testing Advertisement Register Component', () => { }); expect(setTimeoutSpy).toHaveBeenCalled(); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Throws error when the end date is less than the start date', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); const { getByText, queryByText, getByLabelText } = render( @@ -415,7 +428,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -469,11 +482,10 @@ describe('Testing Advertisement Register Component', () => { 'End Date should be greater than or equal to Start Date', ); expect(setTimeoutSpy).toHaveBeenCalled(); - jest.useRealTimers(); + vi.useRealTimers(); }); test('AdvertismentRegister component loads correctly in edit mode', async () => { - jest.useFakeTimers(); render( @@ -487,7 +499,7 @@ describe('Testing Advertisement Register Component', () => { orgIdEdit="1" advertisementMediaEdit="google.com" formStatus="edit" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -497,11 +509,10 @@ describe('Testing Advertisement Register Component', () => { await waitFor(() => { expect(screen.getByTestId('editBtn')).toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Opens and closes modals on button click', async () => { - jest.useFakeTimers(); const { getByText, queryByText } = render( @@ -514,7 +525,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -529,11 +540,10 @@ describe('Testing Advertisement Register Component', () => { await waitFor(() => { expect(queryByText(translations.close)).not.toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Throws error when the end date is less than the start date while editing the advertisement', async () => { - jest.useFakeTimers(); const { getByText, getByLabelText, queryByText } = render( @@ -548,7 +558,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="google.com" - setAfter={jest.fn()} + setAfter={vi.fn()} /> } @@ -596,11 +606,10 @@ describe('Testing Advertisement Register Component', () => { 'End Date should be greater than or equal to Start Date', ); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Media preview renders correctly', async () => { - jest.useFakeTimers(); render( @@ -613,7 +622,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="test.mp4" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -637,5 +646,47 @@ describe('Testing Advertisement Register Component', () => { fireEvent.click(closeButton); expect(mediaPreview).not.toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); + + test('shows success toast on successful update', async () => { + const setAfterMock = vi.fn(); + + render( + + + + + + + + + , + ); + + const editButton = screen.getByTestId('editBtn'); + fireEvent.click(editButton); + + const nameInput = screen.getByLabelText('Enter name of Advertisement'); + fireEvent.change(nameInput, { target: { value: 'Updated Ad' } }); + + const saveButton = screen.getByTestId('addonupdate'); + fireEvent.click(saveButton); + + await waitFor(() => { + // Verify success toast was shown + expect(toast.success).toHaveBeenCalledWith( + 'Advertisement created successfully.', + ); + }); + }); }); diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx b/src/components/AgendaCategory/AgendaCategoryContainer.spec.tsx similarity index 98% rename from src/components/AgendaCategory/AgendaCategoryContainer.test.tsx rename to src/components/AgendaCategory/AgendaCategoryContainer.spec.tsx index d8e27c3cb2..74880558b4 100644 --- a/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx +++ b/src/components/AgendaCategory/AgendaCategoryContainer.spec.tsx @@ -8,9 +8,7 @@ import { fireEvent, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -25,14 +23,15 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import AgendaCategoryContainer from './AgendaCategoryContainer'; import { props, props2 } from './AgendaCategoryContainerProps'; import { MOCKS, MOCKS_ERROR_MUTATIONS } from './AgendaCategoryContainerMocks'; +import { vi, describe, test, expect } from 'vitest'; const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); diff --git a/src/components/AgendaCategory/AgendaCategoryContainerProps.ts b/src/components/AgendaCategory/AgendaCategoryContainerProps.ts index 5181eec153..3d8128128c 100644 --- a/src/components/AgendaCategory/AgendaCategoryContainerProps.ts +++ b/src/components/AgendaCategory/AgendaCategoryContainerProps.ts @@ -1,4 +1,5 @@ type AgendaCategoryConnectionType = 'Organization'; +import { vi } from 'vitest'; export const props = { agendaCategoryConnection: 'Organization' as AgendaCategoryConnectionType, @@ -24,11 +25,11 @@ export const props = { }, }, ], - agendaCategoryRefetch: jest.fn(), + agendaCategoryRefetch: vi.fn(), }; export const props2 = { agendaCategoryConnection: 'Organization' as AgendaCategoryConnectionType, agendaCategoryData: [], - agendaCategoryRefetch: jest.fn(), + agendaCategoryRefetch: vi.fn(), }; diff --git a/src/components/AgendaItems/AgendaItemsContainer.test.tsx b/src/components/AgendaItems/AgendaItemsContainer.spec.tsx similarity index 98% rename from src/components/AgendaItems/AgendaItemsContainer.test.tsx rename to src/components/AgendaItems/AgendaItemsContainer.spec.tsx index 8b391a2073..f8f6ab948d 100644 --- a/src/components/AgendaItems/AgendaItemsContainer.test.tsx +++ b/src/components/AgendaItems/AgendaItemsContainer.spec.tsx @@ -7,9 +7,7 @@ import { fireEvent, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -24,20 +22,20 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import { props, props2 } from './AgendaItemsContainerProps'; import { MOCKS, MOCKS_ERROR } from './AgendaItemsContainerMocks'; import AgendaItemsContainer from './AgendaItemsContainer'; - +import { describe, test, expect, vi } from 'vitest'; const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_ERROR, true); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); //temporarily fixes react-beautiful-dnd droppable method's depreciation error //needs to be fixed in React 19 -jest.spyOn(console, 'error').mockImplementation((message) => { +vi.spyOn(console, 'error').mockImplementation((message) => { if (message.includes('Support for defaultProps will be removed')) { return; } diff --git a/src/components/AgendaItems/AgendaItemsContainerProps.ts b/src/components/AgendaItems/AgendaItemsContainerProps.ts index d6dcf3feca..f19d5ff9df 100644 --- a/src/components/AgendaItems/AgendaItemsContainerProps.ts +++ b/src/components/AgendaItems/AgendaItemsContainerProps.ts @@ -1,4 +1,5 @@ type AgendaItemConnectionType = 'Event'; +import { vi } from 'vitest'; export const props = { agendaItemConnection: 'Event' as AgendaItemConnectionType, @@ -68,7 +69,7 @@ export const props = { }, }, ], - agendaItemRefetch: jest.fn(), + agendaItemRefetch: vi.fn(), agendaItemCategories: [ { _id: 'agendaCategory1', @@ -96,6 +97,6 @@ export const props = { export const props2 = { agendaItemConnection: 'Event' as AgendaItemConnectionType, agendaItemData: [], - agendaItemRefetch: jest.fn(), + agendaItemRefetch: vi.fn(), agendaItemCategories: [], }; diff --git a/src/components/AgendaItems/AgendaItemsCreateModal.test.tsx b/src/components/AgendaItems/AgendaItemsCreateModal.spec.tsx similarity index 96% rename from src/components/AgendaItems/AgendaItemsCreateModal.test.tsx rename to src/components/AgendaItems/AgendaItemsCreateModal.spec.tsx index 5b7339ad67..35ea8c7681 100644 --- a/src/components/AgendaItems/AgendaItemsCreateModal.test.tsx +++ b/src/components/AgendaItems/AgendaItemsCreateModal.spec.tsx @@ -19,6 +19,8 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import AgendaItemsCreateModal from './AgendaItemsCreateModal'; import { toast } from 'react-toastify'; import convertToBase64 from 'utils/convertToBase64'; +import type { MockedFunction } from 'vitest'; +import { describe, test, expect, vi } from 'vitest'; const mockFormState = { title: 'Test Title', @@ -28,9 +30,9 @@ const mockFormState = { urls: ['https://example.com'], agendaItemCategoryIds: ['category'], }; -const mockHideCreateModal = jest.fn(); -const mockSetFormState = jest.fn(); -const mockCreateAgendaItemHandler = jest.fn(); +const mockHideCreateModal = vi.fn(); +const mockSetFormState = vi.fn(); +const mockCreateAgendaItemHandler = vi.fn(); const mockT = (key: string): string => key; const mockAgendaItemCategories = [ { @@ -64,14 +66,14 @@ const mockAgendaItemCategories = [ }, }, ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('utils/convertToBase64'); -const mockedConvertToBase64 = convertToBase64 as jest.MockedFunction< +vi.mock('utils/convertToBase64'); +const mockedConvertToBase64 = convertToBase64 as MockedFunction< typeof convertToBase64 >; diff --git a/src/components/AgendaItems/AgendaItemsPreviewModal.test.tsx b/src/components/AgendaItems/AgendaItemsPreviewModal.spec.tsx similarity index 94% rename from src/components/AgendaItems/AgendaItemsPreviewModal.test.tsx rename to src/components/AgendaItems/AgendaItemsPreviewModal.spec.tsx index 0a7b4646ba..35c67576cc 100644 --- a/src/components/AgendaItems/AgendaItemsPreviewModal.test.tsx +++ b/src/components/AgendaItems/AgendaItemsPreviewModal.spec.tsx @@ -11,6 +11,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import AgendaItemsPreviewModal from './AgendaItemsPreviewModal'; +import { describe, test, expect, vi } from 'vitest'; const mockFormState = { title: 'Test Title', @@ -44,10 +45,10 @@ describe('AgendaItemsPreviewModal', () => { diff --git a/src/components/AgendaItems/AgendaItemsUpdateModal.test.tsx b/src/components/AgendaItems/AgendaItemsUpdateModal.spec.tsx similarity index 96% rename from src/components/AgendaItems/AgendaItemsUpdateModal.test.tsx rename to src/components/AgendaItems/AgendaItemsUpdateModal.spec.tsx index 0f11ea31b2..8d277419fa 100644 --- a/src/components/AgendaItems/AgendaItemsUpdateModal.test.tsx +++ b/src/components/AgendaItems/AgendaItemsUpdateModal.spec.tsx @@ -12,13 +12,14 @@ import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; - import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import AgendaItemsUpdateModal from './AgendaItemsUpdateModal'; import { toast } from 'react-toastify'; import convertToBase64 from 'utils/convertToBase64'; +import type { MockedFunction } from 'vitest'; +import { describe, test, expect, vi } from 'vitest'; const mockFormState = { title: 'Test Title', @@ -67,19 +68,19 @@ const mockAgendaItemCategories = [ }, ]; -const mockHideUpdateModal = jest.fn(); -const mockSetFormState = jest.fn(); -const mockUpdateAgendaItemHandler = jest.fn(); +const mockHideUpdateModal = vi.fn(); +const mockSetFormState = vi.fn(); +const mockUpdateAgendaItemHandler = vi.fn(); const mockT = (key: string): string => key; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('utils/convertToBase64'); -const mockedConvertToBase64 = convertToBase64 as jest.MockedFunction< +vi.mock('utils/convertToBase64'); +const mockedConvertToBase64 = convertToBase64 as MockedFunction< typeof convertToBase64 >; diff --git a/src/components/Avatar/Avatar.spec.tsx b/src/components/Avatar/Avatar.spec.tsx index 001f53b7fa..2721595304 100644 --- a/src/components/Avatar/Avatar.spec.tsx +++ b/src/components/Avatar/Avatar.spec.tsx @@ -8,7 +8,6 @@ import Avatar from './Avatar'; * Unit tests for the `Avatar` component. * * The tests ensure the `Avatar` component renders correctly with various props. - * * Mocked dependencies are used to isolate the component and verify its behavior. * */ diff --git a/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx b/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.spec.tsx similarity index 93% rename from src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx rename to src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.spec.tsx index dc14f6ce17..cbaa628339 100644 --- a/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx +++ b/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.spec.tsx @@ -11,6 +11,7 @@ import { MockedProvider } from '@apollo/react-testing'; import { UPDATE_USER_MUTATION } from 'GraphQl/Mutations/mutations'; import { StaticMockLink } from 'utils/StaticMockLink'; import useLocalStorage from 'utils/useLocalstorage'; +import { describe, expect, it } from 'vitest'; // import { Provider } from 'react-redux'; // import { store } from 'state/store'; const { setItem } = useLocalStorage(); @@ -52,7 +53,7 @@ const MOCKS = [ const link = new StaticMockLink(MOCKS, true); describe('Testing Change Language Dropdown', () => { - test('Component Should be rendered properly', async () => { + it('Component Should be rendered properly', async () => { const { getByTestId } = render( @@ -81,7 +82,7 @@ describe('Testing Change Language Dropdown', () => { }); }); - test('Component Should accept props properly', async () => { + it('Component Should accept props properly', async () => { const props = { parentContainerStyle: 'parentContainerStyle', btnStyle: 'btnStyle', @@ -103,7 +104,7 @@ describe('Testing Change Language Dropdown', () => { getByTestId('dropdown-btn-0').className.includes(props.btnTextStyle); }); - test('Testing when language cookie is not set', async () => { + it('Testing when language cookie is not set', async () => { Object.defineProperty(window.document, 'cookie', { writable: true, value: 'i18next=', @@ -121,7 +122,7 @@ describe('Testing Change Language Dropdown', () => { expect(cookies.get('i18next')).toBe(''); }); - test('Testing change language functionality', async () => { + it('Testing change language functionality', async () => { Object.defineProperty(window.document, 'cookie', { writable: true, value: 'i18next=sp', diff --git a/src/components/CheckIn/CheckInModal.test.tsx b/src/components/CheckIn/CheckInModal.spec.tsx similarity index 89% rename from src/components/CheckIn/CheckInModal.test.tsx rename to src/components/CheckIn/CheckInModal.spec.tsx index 1660c7c4bb..4f5a05328e 100644 --- a/src/components/CheckIn/CheckInModal.test.tsx +++ b/src/components/CheckIn/CheckInModal.spec.tsx @@ -12,6 +12,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { checkInQueryMock } from './mocks'; import { StaticMockLink } from 'utils/StaticMockLink'; +import { vi } from 'vitest'; const link = new StaticMockLink(checkInQueryMock, true); @@ -19,9 +20,14 @@ describe('Testing Check In Attendees Modal', () => { const props = { show: true, eventId: 'event123', - handleClose: jest.fn(), + handleClose: vi.fn(), }; + /** + * Test case for rendering the CheckInModal component and verifying functionality. + * It checks that the modal renders fetched users and verifies the filtering mechanism. + */ + test('The modal should be rendered, and all the fetched users should be shown properly and user filtering should work', async () => { const { queryByText, queryByLabelText } = render( diff --git a/src/components/CheckIn/CheckInWrapper.module.css b/src/components/CheckIn/CheckInWrapper.module.css deleted file mode 100644 index f5f42546c3..0000000000 --- a/src/components/CheckIn/CheckInWrapper.module.css +++ /dev/null @@ -1,13 +0,0 @@ -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.test.tsx b/src/components/CheckIn/CheckInWrapper.spec.tsx similarity index 76% rename from src/components/CheckIn/CheckInWrapper.test.tsx rename to src/components/CheckIn/CheckInWrapper.spec.tsx index 81f53d0043..06b2695f88 100644 --- a/src/components/CheckIn/CheckInWrapper.test.tsx +++ b/src/components/CheckIn/CheckInWrapper.spec.tsx @@ -13,6 +13,19 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { checkInQueryMock } from './mocks'; import { StaticMockLink } from 'utils/StaticMockLink'; +/** + * This file contains unit tests for the CheckInWrapper component. + * + * The tests cover: + * - Rendering and behavior of the modal component. + * - Functionality of the button to open and close the modal. + * - Integration with mocked GraphQL queries for testing Apollo Client. + * + * Purpose: + * These tests ensure that the CheckInWrapper component behaves as expected + * when opening and closing modals, and correctly integrates with its dependencies. + */ + const link = new StaticMockLink(checkInQueryMock, true); describe('Testing CheckIn Wrapper', () => { @@ -20,7 +33,7 @@ describe('Testing CheckIn Wrapper', () => { eventId: 'event123', }; - test('The button to open and close the modal should work properly', async () => { + it('The button to open and close the modal should work properly', async () => { render( diff --git a/src/components/CheckIn/CheckInWrapper.tsx b/src/components/CheckIn/CheckInWrapper.tsx index 859ae1f869..7b35ce1483 100644 --- a/src/components/CheckIn/CheckInWrapper.tsx +++ b/src/components/CheckIn/CheckInWrapper.tsx @@ -1,8 +1,7 @@ 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'; +import style from '../../style/app.module.css'; type PropType = { eventId: string; @@ -21,19 +20,19 @@ export const CheckInWrapper = ({ eventId }: PropType): JSX.Element => { return ( <> {showModal && ( diff --git a/src/components/CheckIn/TableRow.test.tsx b/src/components/CheckIn/TableRow.spec.tsx similarity index 91% rename from src/components/CheckIn/TableRow.test.tsx rename to src/components/CheckIn/TableRow.spec.tsx index 1a08f40a3d..4e11302fe7 100644 --- a/src/components/CheckIn/TableRow.test.tsx +++ b/src/components/CheckIn/TableRow.spec.tsx @@ -11,10 +11,15 @@ import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { MockedProvider } from '@apollo/react-testing'; import { checkInMutationSuccess, checkInMutationUnsuccess } from './mocks'; +import { vi } from 'vitest'; + +/** + * Test suite for the `TableRow` component, focusing on the CheckIn table functionality. + */ describe('Testing Table Row for CheckIn Table', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); test('If the user is not checked in, button to check in should be displayed, and the user should be able to check in successfully', async () => { @@ -26,7 +31,7 @@ describe('Testing Table Row for CheckIn Table', () => { checkIn: null, eventId: `event123`, }, - refetch: jest.fn(), + refetch: vi.fn(), }; const { findByText } = render( @@ -63,7 +68,7 @@ describe('Testing Table Row for CheckIn Table', () => { }, eventId: 'event123', }, - refetch: jest.fn(), + refetch: vi.fn(), }; const { findByText } = render( @@ -81,8 +86,8 @@ describe('Testing Table Row for CheckIn Table', () => { , ); - global.URL.createObjectURL = jest.fn(() => 'mockURL'); - global.window.open = jest.fn(); + global.URL.createObjectURL = vi.fn(() => 'mockURL'); + global.window.open = vi.fn(); expect(await findByText('Checked In')).toBeInTheDocument(); expect(await findByText('Download Tag')).toBeInTheDocument(); @@ -93,7 +98,7 @@ describe('Testing Table Row for CheckIn Table', () => { expect(await findByText('PDF generated successfully!')).toBeInTheDocument(); // Cleanup mocks - jest.clearAllMocks(); + vi.clearAllMocks(); }); test('Upon failing of check in mutation, the appropriate error message should be shown', async () => { @@ -105,7 +110,7 @@ describe('Testing Table Row for CheckIn Table', () => { checkIn: null, eventId: `event123`, }, - refetch: jest.fn(), + refetch: vi.fn(), }; const { findByText } = render( @@ -143,7 +148,7 @@ describe('Testing Table Row for CheckIn Table', () => { }, eventId: `event123`, }, - refetch: jest.fn(), + refetch: vi.fn(), }; const { findByText } = render( @@ -162,8 +167,8 @@ describe('Testing Table Row for CheckIn Table', () => { ); // Mocking the PDF generation function to throw an error - global.URL.createObjectURL = jest.fn(() => 'mockURL'); - global.window.open = jest.fn(); + global.URL.createObjectURL = vi.fn(() => 'mockURL'); + global.window.open = vi.fn(); fireEvent.click(await findByText('Download Tag')); diff --git a/src/components/CheckIn/tagTemplate.ts b/src/components/CheckIn/tagTemplate.ts index 4aa4475e02..611dff7b83 100644 --- a/src/components/CheckIn/tagTemplate.ts +++ b/src/components/CheckIn/tagTemplate.ts @@ -1,22 +1,16 @@ import { Template } from '@pdfme/common'; +import styles from '../../style/app.module.css'; export const tagTemplate: Template = { schemas: [ - { - name: { + [ + { + name: 'name', type: 'text', - position: { x: 14.91, y: 27.03 }, - width: 58.55, - height: 5.67, - alignment: 'center', - fontSize: 16, - characterSpacing: 0, - lineHeight: 1, - fontName: 'Roboto', - fontColor: '#08780b', - }, - }, - ], + className: `${styles['tag-template-name']}`, + } , + ], + ] as any, basePdf: 'data:application/pdf;base64,JVBERi0xLjQKJfbk/N8KMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovVmVyc2lvbiAvMS40Ci9QYWdlcyAyIDAgUgovU3RydWN0VHJlZVJvb3QgMyAwIFIKL01hcmtJbmZvIDQgMCBSCi9MYW5nIChlbikKL1ZpZXdlclByZWZlcmVuY2VzIDUgMCBSCj4+CmVuZG9iago2IDAgb2JqCjw8Ci9DcmVhdG9yIChDYW52YSkKL1Byb2R1Y2VyIChDYW52YSkKL0NyZWF0aW9uRGF0ZSAoRDoyMDIzMDYyMDA3MjgxMyswMCcwMCcpCi9Nb2REYXRlIChEOjIwMjMwNjIwMDcyODEzKzAwJzAwJykKL0tleXdvcmRzIChEQUZjMjhYSXViTSxCQUUycS01WEdhaykKL0F1dGhvciAoRXNoYWFuIEFnZ2Fyd2FsKQovVGl0bGUgKEJsYW5rIE5hbWUgVGFnIGluIEVtZXJhbGQgTWludCBHcmVlbiBBc3BpcmF0aW9uYWwgRWxlZ2FuY2UgU3R5bGUpCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9UeXBlIC9QYWdlcwovS2lkcyBbNyAwIFJdCi9Db3VudCAxCj4+CmVuZG9iagozIDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RUcmVlUm9vdAovUGFyZW50VHJlZSA4IDAgUgovUGFyZW50VHJlZU5leHRLZXkgMQovSyBbOSAwIFJdCi9JRFRyZWUgMTAgMCBSCj4+CmVuZG9iago0IDAgb2JqCjw8Ci9NYXJrZWQgdHJ1ZQovU3VzcGVjdHMgZmFsc2UKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL0Rpc3BsYXlEb2NUaXRsZSB0cnVlCj4+CmVuZG9iago3IDAgb2JqCjw8Ci9UeXBlIC9QYWdlCi9SZXNvdXJjZXMgMTEgMCBSCi9NZWRpYUJveCBbMC4wIDcuOTIwMDAyNSAyNTIuMCAxNTEuOTJdCi9Db250ZW50cyAxMiAwIFIKL1N0cnVjdFBhcmVudHMgMAovUGFyZW50IDIgMCBSCi9UYWJzIC9TCi9CbGVlZEJveCBbMC4wIDcuOTIwMDAyNSAyNTIuMCAxNTEuOTJdCi9UcmltQm94IFswLjAgNy45MjAwMDI1IDI1Mi4wIDE1MS45Ml0KL0Nyb3BCb3ggWzAuMCA3LjkyMDAwMjUgMjUyLjAgMTUxLjkyXQovUm90YXRlIDAKL0Fubm90cyBbXQo+PgplbmRvYmoKOCAwIG9iago8PAovTGltaXRzIFswIDBdCi9OdW1zIFswIFsxMyAwIFIgMTQgMCBSIDE1IDAgUiAxNiAwIFIgMTcgMCBSIDE4IDAgUiAxOSAwIFJdCl0KPj4KZW5kb2JqCjkgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RvY3VtZW50Ci9MYW5nIChlbikKL1AgMyAwIFIKL0sgWzIwIDAgUl0KL0lEIChub2RlMDAwMDE3MzgpCj4+CmVuZG9iagoxMCAwIG9iago8PAovTmFtZXMgWyhub2RlMDAwMDE3MzgpIDkgMCBSIChub2RlMDAwMDE3MzkpIDEzIDAgUiAobm9kZTAwMDAxNzQwKSAyMCAwIFIgKG5vZGUwMDAwMTc0MSkgMjEgMCBSIChub2RlMDAwMDE3NDIpIDIyIDAgUgoobm9kZTAwMDAxNzQzKSAyMyAwIFIgKG5vZGUwMDAwMTc0NCkgMjQgMCBSIChub2RlMDAwMDE3NDUpIDI1IDAgUiAobm9kZTAwMDAxNzQ2KSAyNiAwIFIgKG5vZGUwMDAwMTc0NykgMjcgMCBSCihub2RlMDAwMDE3NjEpIDI4IDAgUiAobm9kZTAwMDAxNzYyKSAyOSAwIFIgKG5vZGUwMDAwMTc2MykgMzAgMCBSIChub2RlMDAwMDE3NjQpIDMxIDAgUiAobm9kZTAwMDAxNzY1KSAzMiAwIFIKKG5vZGUwMDAwMTc2NikgMzMgMCBSIChub2RlMDAwMDE3NjcpIDE0IDAgUiAobm9kZTAwMDAxNzY4KSAzNCAwIFIgKG5vZGUwMDAwMTc2OSkgMzUgMCBSIChub2RlMDAwMDE3NzApIDM2IDAgUgoobm9kZTAwMDAxNzcxKSAxNSAwIFIgKG5vZGUwMDAwMTc3MikgMzcgMCBSIChub2RlMDAwMDE3NzMpIDM4IDAgUiAobm9kZTAwMDAxNzc0KSAzOSAwIFIgKG5vZGUwMDAwMTc3NSkgMTYgMCBSCihub2RlMDAwMDE3NzYpIDE3IDAgUiAobm9kZTAwMDAxNzc3KSA0MCAwIFIgKG5vZGUwMDAwMTc3OCkgMTggMCBSIChub2Rl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Tm9uU3RydWN0Ci9QIDQwIDAgUgovSyBbNTMgMCBSXQovSUQgKG5vZGUwMDAwMTc3OCkKPj4KZW5kb2JqCjE5IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9Ob25TdHJ1Y3QKL1AgNDQgMCBSCi9LIFs1NCAwIFJdCi9JRCAobm9kZTAwMDAxNzgzKQo+PgplbmRvYmoKMjAgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCA5IDAgUgovSyBbMjEgMCBSXQovSUQgKG5vZGUwMDAwMTc0MCkKPj4KZW5kb2JqCjIxIDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjAgMCBSCi9LIFsyMiAwIFJdCi9JRCAobm9kZTAwMDAxNzQxKQo+PgplbmRvYmoKMjIgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyMSAwIFIKL0sgWzIzIDAgUl0KL0lEIChub2RlMDAwMDE3NDIpCj4+CmVuZG9iagoyMyAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDIyIDAgUgovSyBbMjQgMCBSXQovSUQgKG5vZGUwMDAwMTc0MykKPj4KZW5kb2JqCjI0IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjMgMCBSCi9LIFsyNSAwIFJdCi9JRCAobm9kZTAwMDAxNzQ0KQo+PgplbmRvYmoKMjUgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyNCAwIFIKL0sgWzI2IDAgUl0KL0lEIChub2RlMDAwMDE3NDUpCj4+CmVuZG9iagoyNiAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDI1IDAgUgovSyBbMjcgMCBSXQovSUQgKG5vZGUwMDAwMTc0NikKPj4KZW5kb2JqCjI3IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjYgMCBSCi9LIFsyOCAwIFIgMzEgMCBSIDM0IDAgUiAzNyAwIFIgNDEgMCBSXQovSUQgKG5vZGUwMDAwMTc0NykKPj4KZW5kb2JqCjI4IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjcgMCBSCi9LIFsyOSAwIFJdCi9JRCAobm9kZTAwMDAxNzYxKQo+PgplbmRvYmoKMjkgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyOCAwIFIKL0sgWzMwIDAgUl0KL0lEIChub2RlMDAwMDE3NjIpCj4+CmVuZG9iagozMCAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDI5IDAgUgovSyBbMTMgMCBSXQovSUQgKG5vZGUwMDAwMTc2MykKPj4KZW5kb2JqCjMxIDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjcgMCBSCi9LIFszMiAwIFJdCi9JRCAobm9kZTAwMDAxNzY0KQo+PgplbmRvYmoKMzIgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAzMSAwIFIKL0sgWzMzIDAgUl0KL0lEIChub2RlMDAwMDE3NjUpCj4+CmVuZG9iagozMyAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvUAovUCAzMiAwIFIKL0sgWzE0IDAgUl0KL0lEIChub2RlMDAwMDE3NjYpCj4+CmVuZG9iagozNCAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDI3IDAgUgovSyBbMzUgMCBSXQovSUQgKG5vZGUwMDAwMTc2OCkKPj4KZW5kb2JqCjM1IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMzQgMCBSCi9LIFszNiAwIFJdCi9JRCAobm9kZTAwMDAxNzY5KQo+PgplbmRvYmoKMzYgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL1AKL1AgMzUgMCBSCi9LIFsxNSAwIFJdCi9JRCAobm9kZTAwMDAxNzcwKQo+PgplbmRvYmoKMzcgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyNyAwIFIKL0sgWzM4IDAgUl0KL0lEIChub2RlMDAwMDE3NzIpCj4+CmVuZG9iagozOCAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDM3IDAgUgovSyBbMzkgMCBSIDQwIDAgUl0KL0lEIChub2RlMDAwMDE3NzMpCj4+CmVuZG9iagozOSAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvUAovUCAzOCAwIFIKL0sgWzE2IDAgUiAxNyAwIFJdCi9JRCAobm9kZTAwMDAxNzc0KQo+PgplbmRvYmoKNDAgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL1AKL1AgMzggMCBSCi9LIFsxOCAwIFJdCi9JRCAobm9kZTAwMDAxNzc3KQo+PgplbmRvYmoKNDEgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyNyAwIFIKL0sgWzQyIDAgUl0KL0lEIChub2RlMDAwMDE3NzkpCj4+CmVuZG9iago0MiAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDQxIDAgUgovSyBbNDMgMCBSXQovSUQgKG5vZGUwMDAwMTc4MCkKPj4KZW5kb2JqCjQzIDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgNDIgMCBSCi9LIFs0NCAwIFJdCi9JRCAobm9kZTAwMDAxNzgxKQo+PgplbmRvYmoKNDQgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL1AKL1AgNDMgMCBSCi9LIFsxOSAwIFJdCi9JRCAobm9kZTAwMDAxNzgyKQo+PgplbmRvYmoKNDUgMCBvYmoKPDwKL0czIDU1IDAgUgovRzQgNTYgMCBSCj4+CmVuZG9iago0NiAwIG9iago8PAovTGVuZ3RoIDMwMTg4Ci9UeXBlIC9YT2JqZWN0Ci9TdWJ0eXBlIC9JbWFnZQovV2lkdGggMzQ3Ci9IZWlnaHQgMjMzCi9Db2xvclNwYWNlIC9EZXZpY2VSR0IKL1NNYXNrIDU3IDAgUgovQml0c1BlckNvbXBvbmVudCA4Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCj4+CnN0cmVhbQ0KeJzsfQl4FNeVbgmpN6GuajDYY1tIIpPJm8dbJhkyb5LJTIaZJCaYRRvCwTEOjmNi42DAZjGLpNbGamOMgVgJGGMMxo3QvqKltQsB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arVJSZxajbW01KrRlKs9h/BjZkdpqcM8035UbS3VWNRqi6bMzXLBceLFI+QgnL9QSjQ3F284UObxlJW5XMTrMpvcbpMZXofoP5F+JXQNCmVuZHN0cmVhbQplbmRvYmoKeHJlZgowIDY0CjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxNSAwMDAwMCBuDQowMDAwMDAwNDE5IDAwMDAwIG4NCjAwMDAwMDA0NzYgMDAwMDAgbg0KMDAwMDAwMDU4NCAwMDAwMCBuDQowMDAwMDAwNjM0IDAwMDAwIG4NCjAwMDAwMDAxNTIgMDAwMDAgbg0KMDAwMDAwMDY3NyAwMDAwMCBuDQowMDAwMDAwOTYwIDAwMDAwIG4NCjAwMDAwMDEwNTcgMDAwMDAgbg0KMDAwMDAwMTE2MCAwMDAwMCBuDQowMDAwMDAxOTE2IDAwMDAwIG4NCjAwMDAwMDIwNDEgMDAwMDAgbg0KMDAwMDAwMzA5NCAwMDAwMCBuDQowMDAwMDAzMTg2IDAwMDAwIG4NCjAwMDAwMDMyODEgMDAwMDAgbg0KMDAwMDAwMzM3NiAwMDAwMCBuDQowMDAwMDAzNDcxIDAwMDAwIG4NCjAwMDAwMDM1NjYgMDAwMDAgbg0KMDAwMDAwMzY2MSAwMDAwMCBuDQowMDAwMDAzNzU2IDAwMDAwIG4NCjAwMDAwMDM4NDQgMDAwMDAgbg0KMDAwMDAwMzkzMyAwMDAwMCBuDQowMDAwMDA0MDIyIDAwMDAwIG4NCjAwMDAwMDQxMTEgMDAwMDAgbg0KMDAwMDAwNDIwMCAwMDAwMCBuDQowMDAwMDA0Mjg5IDAwMDAwIG4NCjAwMDAwMDQzNzggMDAwMDAgbg0KMDAwMDAwNDQ5NSAwMDAwMCBuDQowMDAwMDA0NTg0IDAwMDAwIG4NCjAwMDAwMDQ2NzMgMDAwMDAgbg0KMDAwMDAwNDc2MiAwMDAwMCBuDQowMDAwMDA0ODUxIDAwMDAwIG4NCjAwMDAwMDQ5NDAgMDAwMDAgbg0KMDAwMDAwNTAyNyAwMDAwMCBuDQowMDAwMDA1MTE2IDAwMDAwIG4NCjAwMDAwMDUyMDUgMDAwMDAgbg0KMDAwMDAwNTI5MiAwMDAwMCBuDQowMDAwMDA1MzgxIDAwMDAwIG4NCjAwMDAwMDU0NzcgMDAwMDAgbg0KMDAwMDAwNTU3MSAwMDAwMCBuDQowMDAwMDA1NjU4IDAwMDAwIG4NCjAwMDAwMDU3NDcgMDAwMDAgbg0KMDAwMDAwNTgzNiAwMDAwMCBuDQowMDAwMDA1OTI1IDAwMDAwIG4NCjAwMDAwMDYwMTIgMDAwMDAgbg0KMDAwMDAwNjA1NiAwMDAwMCBuDQowMDAwMDM2NDMyIDAwMDAwIG4NCjAwMDAwMzY0NjUgMDAwMDAgbg0KMDAwMDAzNjUxNiAwMDAwMCBuDQowMDAwMDM2NTY3IDAwMDAwIG4NCjAwMDAwMzY2MTggMDAwMDAgbg0KMDAwMDAzNjY2OSAwMDAwMCBuDQowMDAwMDM2NzIwIDAwMDAwIG4NCjAwMDAwMzY3NzEgMDAwMDAgbg0KMDAwMDAzNjgyMiAwMDAwMCBuDQowMDAwMDM2ODYyIDAwMDAwIG4NCjAwMDAwMzY5NDEgMDAwMDAgbg0KMDAwMDA0OTMxNiAwMDAwMCBuDQowMDAwMDQ5NDY5IDAwMDAwIG4NCjAwMDAwNTAwMzcgMDAwMDAgbg0KMDAwMDA1MDQ2NSAwMDAwMCBuDQowMDAwMDUwNzIwIDAwMDAwIG4NCjAwMDAwNTA3OTUgMDAwMDAgbg0KdHJhaWxlcgo8PAovUm9vdCAxIDAgUgovSW5mbyA2IDAgUgovSUQgWzwwQ0M3NzdBRDZENEFBMThFRkFGQ0ExODQ3QjI1QkMxQz4gPDBDQzc3N0FENkQ0QUExOEVGQUZDQTE4NDdCMjVCQzFDPl0KL1NpemUgNjQKPj4Kc3RhcnR4cmVmCjU0MDk2CiUlRU9GCg==', }; diff --git a/src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx b/src/components/CollapsibleDropdown/CollapsibleDropdown.spec.tsx similarity index 87% rename from src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx rename to src/components/CollapsibleDropdown/CollapsibleDropdown.spec.tsx index efee248ffb..b288fb9de5 100644 --- a/src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx +++ b/src/components/CollapsibleDropdown/CollapsibleDropdown.spec.tsx @@ -8,21 +8,33 @@ import { store } from 'state/store'; import { Provider } from 'react-redux'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; +import { describe, expect, test, vi, afterEach } from 'vitest'; +import type { Location } from '@remix-run/router'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: '/orgstore', - state: {}, - key: '', - search: '', - hash: '', - }), -})); +afterEach(() => { + vi.resetModules(); +}); + +const currentLocation: Location = { + pathname: '/orgstore', + state: {}, + key: '', + search: '', + hash: '', +}; + +vi.mock('react-router-dom', async (importOriginal) => { + const mod = (await importOriginal()) as object; + + return { + ...mod, + useLocation: () => currentLocation, + }; +}); const props: InterfaceCollapsibleDropdown = { showDropdown: true, - setShowDropdown: jest.fn(), + setShowDropdown: vi.fn(), target: { name: 'DropDown Category', url: undefined, diff --git a/src/components/ContriStats/ContriStats.module.css b/src/components/ContriStats/ContriStats.module.css deleted file mode 100644 index 7d6c83ea8e..0000000000 --- a/src/components/ContriStats/ContriStats.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.fonts { - color: #707070; -} - -.fonts > span { - font-weight: 600; -} diff --git a/src/components/ContriStats/ContriStats.test.tsx b/src/components/ContriStats/ContriStats.spec.tsx similarity index 96% rename from src/components/ContriStats/ContriStats.test.tsx rename to src/components/ContriStats/ContriStats.spec.tsx index 8edb853684..4a9f1fc94f 100644 --- a/src/components/ContriStats/ContriStats.test.tsx +++ b/src/components/ContriStats/ContriStats.spec.tsx @@ -6,6 +6,7 @@ import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'; import type { NormalizedCacheObject } from '@apollo/client'; import i18nForTest from 'utils/i18nForTest'; import { BACKEND_URL } from 'Constant/constant'; +import { describe, test, expect } from 'vitest'; const client: ApolloClient = new ApolloClient({ cache: new InMemoryCache(), diff --git a/src/components/ContriStats/ContriStats.tsx b/src/components/ContriStats/ContriStats.tsx index a2db307d91..91e6b9872f 100644 --- a/src/components/ContriStats/ContriStats.tsx +++ b/src/components/ContriStats/ContriStats.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import styles from './ContriStats.module.css'; +import styles from '../../style/app.module.css'; interface InterfaceContriStatsProps { id: string; @@ -13,11 +13,17 @@ interface InterfaceContriStatsProps { /** * A component that displays contribution statistics. * - * @param props - The properties passed to the component, including `recentAmount`, `highestAmount`, and `totalAmount`. + * @param recentAmount - The most recent contribution amount. + * @param highestAmount - The highest contribution amount. + * @param totalAmount - The total contribution amount. * * @returns JSX.Element - The rendered component displaying the contribution stats. */ -function ContriStats(props: InterfaceContriStatsProps): JSX.Element { +function ContriStats({ + recentAmount, + highestAmount, + totalAmount, +}: InterfaceContriStatsProps): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'contriStats', }); @@ -25,15 +31,16 @@ function ContriStats(props: InterfaceContriStatsProps): JSX.Element { return ( <>

- {t('recentContribution')}: $ {props.recentAmount} + {t('recentContribution')}: $ {recentAmount}

- {t('highestContribution')}: $ {props.highestAmount} + {t('highestContribution')}: $ {highestAmount}

- {t('totalContribution')}: $ {props.totalAmount} + {t('totalContribution')}: $ {totalAmount}

); } + export default ContriStats; diff --git a/src/components/DynamicDropDown/DynamicDropDown.test.tsx b/src/components/DynamicDropDown/DynamicDropDown.spec.tsx similarity index 91% rename from src/components/DynamicDropDown/DynamicDropDown.test.tsx rename to src/components/DynamicDropDown/DynamicDropDown.spec.tsx index dac98ca9e6..85c3208a20 100644 --- a/src/components/DynamicDropDown/DynamicDropDown.test.tsx +++ b/src/components/DynamicDropDown/DynamicDropDown.spec.tsx @@ -11,11 +11,12 @@ import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import userEvent from '@testing-library/user-event'; +import { vi, expect, it } from 'vitest'; describe('DynamicDropDown component', () => { - test('renders and handles selection correctly', async () => { + it('renders and handles selection correctly', async () => { const formData = { fieldName: 'value2' }; - const setFormData = jest.fn(); + const setFormData = vi.fn(); render( @@ -60,10 +61,10 @@ describe('DynamicDropDown component', () => { expect(dropdownButton).toHaveTextContent('Label 2'); }); }); - test('calls custom handleChange function when provided', async () => { + it('calls custom handleChange function when provided', async () => { const formData = { fieldName: 'value1' }; - const setFormData = jest.fn(); - const customHandleChange = jest.fn(); + const setFormData = vi.fn(); + const customHandleChange = vi.fn(); render( @@ -103,9 +104,9 @@ describe('DynamicDropDown component', () => { ); expect(setFormData).not.toHaveBeenCalled(); }); - test('handles keyboard navigation correctly', async () => { + it('handles keyboard navigation correctly', async () => { const formData = { fieldName: 'value1' }; - const setFormData = jest.fn(); + const setFormData = vi.fn(); render( diff --git a/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx b/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.spec.tsx similarity index 90% rename from src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx rename to src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.spec.tsx index 19d2249a43..4119800dd1 100644 --- a/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx +++ b/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.spec.tsx @@ -8,6 +8,7 @@ import availableFieldTypes from 'utils/fieldTypes'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import type { InterfaceCustomFieldData } from 'utils/interfaces'; +import { describe, it, expect } from 'vitest'; async function wait(ms = 100): Promise { await act(() => { @@ -18,7 +19,7 @@ async function wait(ms = 100): Promise { } describe('Testing Custom Field Dropdown', () => { - test('Component Should be rendered properly', async () => { + it('Component Should be rendered properly', async () => { const customFieldData = { type: 'Number', name: 'Age', @@ -26,11 +27,10 @@ describe('Testing Custom Field Dropdown', () => { const setCustomFieldData: Dispatch< SetStateAction - > = (val) => { - { - val; - } + > = () => { + // Intentionally left blank for testing purposes }; + const props = { customFieldData: customFieldData as InterfaceCustomFieldData, setCustomFieldData: setCustomFieldData, diff --git a/src/components/EventCalendar/EventCalendar.module.css b/src/components/EventCalendar/EventCalendar.module.css index 921af48bae..607d750f85 100644 --- a/src/components/EventCalendar/EventCalendar.module.css +++ b/src/components/EventCalendar/EventCalendar.module.css @@ -271,14 +271,6 @@ .expand_event_list { display: block; } - -.list_container { - padding: 5px; - width: fit-content; - display: flex; - flex-direction: row; -} - .event_list_hour { display: flex; flex-direction: row; @@ -316,7 +308,8 @@ flex-grow: 1; } -@media only screen and (max-width: 700px) { +@media only screen and (max-width: var(--mobile-breakpoint)) { + /** @breakpoint --mobile-breakpoint: 768px */ .event_list { display: none; } @@ -331,7 +324,7 @@ } } -@media only screen and (max-width: 500px) { +@media only screen and (max-width: var(--small-mobile-breakpoint)) { .btn__more { font-size: 12px; } @@ -344,7 +337,22 @@ gap: 10px; margin-top: 35px; } +.base_card { + flex: 1; + padding: 20px; + border-radius: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); +} + +.holidays_card { + composes: base_card; + background-color: var(--holiday-card-bg); +} +.events_card { + composes: base_card; + background-color: #ffffff; +} .card__holidays { background-color: rgba(246, 242, 229, 1); display: flex; @@ -415,9 +423,150 @@ border-radius: 10px; } -.userEvents__color { - height: 15px; - width: 40px; - background: rgba(146, 200, 141, 0.5); +.baseIndicator { + border-radius: 5px; + width: 20px; + height: 12px; + display: inline-block; +} + +.holidayText { + font-size: 14px; + color: #555555; +} +.eventsLegend { + display: flex; + align-items: center; + gap: 8px; +} + +.list_container { + padding: 5px; + width: fit-content; + display: flex; + align-items: center; + gap: var(--indicator-spacing); +} + +.holidayIndicator { + composes: baseIndicator; + background-color: rgba(0, 0, 0, 0.15); +} + +:root { + /* Color scheme for holiday-related elements */ + --color-user-event: rgba(139, 195, 74, 1); + /* Holiday colors */ + --color-holiday-indicator: rgba(0, 0, 0, 0.15); + --color-holiday-date: rgba(255, 152, 0, 1); + /* Breakpoints for responsive design */ + --mobile-breakpoint: 700px; + --small-mobile-breakpoint: 480px; + --text-color-primary: rgba(51, 51, 51, 1); + --text-color-secondary: rgba(85, 85, 85, 1); + /* Card styles */ + --card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + --holiday-card-bg: #f6f2e5; + --holiday-date-color: #d35400; + --indicator-spacing: 8px; + /* Interactive states */ + --hover-bg-color: rgba(0, 0, 0, 0.05); +} +.organizationIndicator { + composes: baseIndicator; + background-color: rgba(82, 172, 255, 0.5); +} + +.legendText { + font-size: 14px; + color: #555555; +} +@media only screen and (max-width: var(--mobile-breakpoint)) { + .list_container, + .eventsLegend { + gap: 4px; + } + + .holidayIndicator, + .organizationIndicator { + width: 16px; + height: 10px; + } + + .holidayText, + .legendText { + font-size: 12px; + } +} +.card_title { + font-size: 16px; + font-weight: 600; + color: var(--text-color-primary); + margin-bottom: 15px; +} + +.card_list { + list-style: none; + padding: 0; + margin: 0; +} + +.card_list_item { + display: flex; + align-items: center; + margin-bottom: 10px; + font-size: 14px; + color: var(--text-color-secondary); +} + +.holiday_date { + font-weight: 500; + margin-right: 10px; + color: var(--holiday-date-color); +} + +.calendar_infocards { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + gap: 20px; + padding: 20px; + background-color: var(--grey-bg-color); +} +.holidays_card, +.events_card { + flex: 1; + padding: 20px; border-radius: 10px; + box-shadow: var(--card-shadow); +} + +.holidays_card { + background-color: var(--holiday-card-bg); +} + +.events_card { + background-color: white; +} + +.legend { + display: flex; + flex-direction: column; + gap: 12px; +} + +.userEvents__color { + composes: baseIndicator; + display: inline-block; + background-color: rgba(139, 195, 74, 1); +} + +.card_list_item:hover { + background-color: var(--hover-bg-color); + transition: background-color 0.2s ease; +} +.card_list_item:focus-visible { + background-color: var(--hover-bg-color); + transition: background-color 0.2s ease; } diff --git a/src/components/EventCalendar/EventCalendar.test.tsx b/src/components/EventCalendar/EventCalendar.spec.tsx similarity index 99% rename from src/components/EventCalendar/EventCalendar.test.tsx rename to src/components/EventCalendar/EventCalendar.spec.tsx index 8e2395968a..4dfd0548d6 100644 --- a/src/components/EventCalendar/EventCalendar.test.tsx +++ b/src/components/EventCalendar/EventCalendar.spec.tsx @@ -13,6 +13,7 @@ import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import { weekdays, months } from './constants'; import { BrowserRouter as Router } from 'react-router-dom'; +import { vi } from 'vitest'; const eventData = [ { @@ -122,7 +123,7 @@ describe('Calendar', () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should render the current month and year', () => { diff --git a/src/components/EventCalendar/EventCalendar.tsx b/src/components/EventCalendar/EventCalendar.tsx index 575bda391e..ebcf8be942 100644 --- a/src/components/EventCalendar/EventCalendar.tsx +++ b/src/components/EventCalendar/EventCalendar.tsx @@ -1,16 +1,14 @@ import EventListCard from 'components/EventListCard/EventListCard'; import dayjs from 'dayjs'; +import React, { useState, useEffect, useMemo } from 'react'; import Button from 'react-bootstrap/Button'; -import React, { useState, useEffect } from 'react'; import styles from './EventCalendar.module.css'; import { ChevronLeft, ChevronRight } from '@mui/icons-material'; -import CurrentHourIndicator from 'components/CurrentHourIndicator/CurrentHourIndicator'; import { ViewType } from 'screens/OrganizationEvents/OrganizationEvents'; import HolidayCard from '../HolidayCards/HolidayCard'; -import { holidays, hours, months, weekdays } from './constants'; +import { holidays, months, weekdays } from './constants'; import type { InterfaceRecurrenceRule } from 'utils/recurrenceUtils'; import YearlyEventCalender from './YearlyEventCalender'; - interface InterfaceEventListCardProps { userRole?: string; key?: string; @@ -75,7 +73,6 @@ const Calendar: React.FC = ({ ); const [expanded, setExpanded] = useState(-1); const [windowWidth, setWindowWidth] = useState(window.screen.width); - useEffect(() => { function handleResize(): void { setWindowWidth(window.screen.width); @@ -138,9 +135,21 @@ const Calendar: React.FC = ({ } }; - /** - * Moves the calendar view to the next month. - */ + const filteredHolidays = useMemo(() => { + return Array.isArray(holidays) + ? holidays.filter((holiday) => { + if (!holiday.date) { + if (process.env.NODE_ENV !== 'test') { + console.warn(`Holiday "${holiday.name}" has no date specified.`); + } + return false; + } + const holidayMonth = dayjs(holiday.date, 'MM-DD', true).month(); + return holidayMonth === currentMonth; + }) + : []; + }, [holidays, currentMonth]); + const handleNextMonth = (): void => { if (currentMonth === 11) { setCurrentMonth(0); @@ -149,10 +158,6 @@ const Calendar: React.FC = ({ setCurrentMonth(currentMonth + 1); } }; - - /** - * Moves the calendar view to the previous date. - */ const handlePrevDate = (): void => { if (currentDate > 1) { setCurrentDate(currentDate - 1); @@ -193,9 +198,6 @@ const Calendar: React.FC = ({ } }; - /** - * Moves the calendar view to today's date. - */ const handleTodayButton = (): void => { setCurrentYear(today.getFullYear()); setCurrentMonth(today.getMonth()); @@ -264,6 +266,17 @@ const Calendar: React.FC = ({ ); }) || []; + const shouldShowViewMore = useMemo(() => { + return ( + allDayEventsList.length > 2 || + (windowWidth <= 700 && allDayEventsList.length > 0) + ); + }, [allDayEventsList.length, windowWidth]); + + const handleExpandClick: () => void = () => { + toggleExpand(-100); + }; + return ( <>
@@ -284,7 +297,6 @@ const Calendar: React.FC = ({ ? styles.expand_list_container : styles.list_container } - style={{ width: 'fit-content' }} >
= ({ : styles.event_list_hour } > - {expanded === -100 - ? allDayEventsList - : allDayEventsList?.slice(0, 1)} + {Array.isArray(allDayEventsList) && + allDayEventsList.length > 0 ? ( + expanded === -100 ? ( + allDayEventsList + ) : ( + allDayEventsList.slice(0, 1) + ) + ) : ( +

+ No events available +

+ )}
- {(allDayEventsList?.length > 2 || - (windowWidth <= 700 && allDayEventsList?.length > 0)) && ( + {Array.isArray(allDayEventsList) && ( )}
- {hours.map((hour, index) => { - const timeEventsList: JSX.Element[] = - events - ?.filter((datas) => { - const currDate = new Date( - currentYear, - currentMonth, - currentDate, - ); - - if ( - parseInt(datas.startTime?.slice(0, 2) as string).toString() == - (index % 24).toString() && - datas.startDate == dayjs(currDate).format('YYYY-MM-DD') - ) { - return datas; - } - }) - .map((datas: InterfaceEventListCardProps) => { - const attendees: { _id: string }[] = []; - datas.attendees?.forEach((attendee: { _id: string }) => { - const r = { - _id: attendee._id, - }; - - attendees.push(r); - }); - - return ( - - ); - }) || []; - return ( -
-
-

{`${hour}`}

+
+
+

Holidays

+
    + {filteredHolidays.map((holiday, index) => ( +
  • + + {months[parseInt(holiday.date.slice(0, 2), 10) - 1]}{' '} + {holiday.date.slice(3)} + + {holiday.name} +
  • + ))} +
+
+ +
+

Events

+
+
+ + Holidays
-
-
0 - ? styles.event_list_parent_current - : styles.event_list_parent - } - > - {index % 24 == new Date().getHours() && - new Date().getDate() == currentDate && ( - - )} -
-
- {} - {expanded === index - ? timeEventsList - : timeEventsList?.slice(0, 1)} -
- {(timeEventsList?.length > 1 || - (windowWidth <= 700 && timeEventsList?.length > 0)) && ( - - )} -
+
+ + + Events Created by Organization + +
+
+ + + Events Created by User +
- ); - })} +
+
); }; @@ -448,10 +407,10 @@ const Calendar: React.FC = ({ return days.map((date, index) => { const className = [ date.getDay() === 0 || date.getDay() === 6 ? styles.day_weekends : '', - date.toLocaleDateString() === today.toLocaleDateString() //Styling for today day cell + date.toLocaleDateString() === today.toLocaleDateString() ? styles.day__today : '', - date.getMonth() !== currentMonth ? styles.day__outside : '', //Styling for days outside the current month + date.getMonth() !== currentMonth ? styles.day__outside : '', selectedDate?.getTime() === date.getTime() ? styles.day__selected : '', styles.day, ].join(' '); @@ -504,13 +463,12 @@ const Calendar: React.FC = ({ ); }) || []; - const holidayList: JSX.Element[] = holidays - .filter((holiday) => { - if (holiday.date == dayjs(date).format('MM-DD')) return holiday; - }) + const holidayList: JSX.Element[] = filteredHolidays + .filter((holiday) => holiday.date === dayjs(date).format('MM-DD')) .map((holiday) => { return ; }); + return (
= ({ )}
{viewType == ViewType.MONTH ? ( -
+ <>
{weekdays.map((weekday, index) => (
@@ -611,18 +569,14 @@ const Calendar: React.FC = ({ ))}
{renderDays()}
-
+ + ) : viewType == ViewType.YEAR ? ( + ) : ( - // -
- {viewType == ViewType.YEAR ? ( - - ) : ( -
{renderHours()}
- )} -
+
{renderHours()}
)}
+
{viewType == ViewType.YEAR ? ( diff --git a/src/components/EventCalendar/EventHeader.test.tsx b/src/components/EventCalendar/EventHeader.spec.tsx similarity index 92% rename from src/components/EventCalendar/EventHeader.test.tsx rename to src/components/EventCalendar/EventHeader.spec.tsx index e18d066306..be1ba4bd78 100644 --- a/src/components/EventCalendar/EventHeader.test.tsx +++ b/src/components/EventCalendar/EventHeader.spec.tsx @@ -4,11 +4,20 @@ import EventHeader from './EventHeader'; import { ViewType } from '../../screens/OrganizationEvents/OrganizationEvents'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; +import { vi } from 'vitest'; describe('EventHeader Component', () => { const viewType = ViewType.MONTH; - const handleChangeView = jest.fn(); - const showInviteModal = jest.fn(); + + /** + * Mock function to handle view type changes. + */ + const handleChangeView = vi.fn(); + + /** + * Mock function to handle the display of the invite modal. + */ + const showInviteModal = vi.fn(); it('renders correctly', () => { const { getByTestId } = render( diff --git a/src/components/EventCalendar/YearlyEventCalender.tsx b/src/components/EventCalendar/YearlyEventCalender.tsx index 2852ca8cfb..facf75038c 100644 --- a/src/components/EventCalendar/YearlyEventCalender.tsx +++ b/src/components/EventCalendar/YearlyEventCalender.tsx @@ -63,7 +63,7 @@ enum Role { /** * Interface for event attendees. - */ +// */ // interface InterfaceIEventAttendees { // userId: string; // user?: string; diff --git a/src/components/EventDashboardScreen/EventDashboardScreen.test.tsx b/src/components/EventDashboardScreen/EventDashboardScreen.spec.tsx similarity index 85% rename from src/components/EventDashboardScreen/EventDashboardScreen.test.tsx rename to src/components/EventDashboardScreen/EventDashboardScreen.spec.tsx index 6b48b034a9..7fc5028658 100644 --- a/src/components/EventDashboardScreen/EventDashboardScreen.test.tsx +++ b/src/components/EventDashboardScreen/EventDashboardScreen.spec.tsx @@ -1,8 +1,8 @@ import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; import { MockedProvider } from '@apollo/react-testing'; import { fireEvent, render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; -import 'jest-location-mock'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; @@ -11,27 +11,32 @@ import EventDashboardScreen from './EventDashboardScreen'; import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; import useLocalStorage from 'utils/useLocalstorage'; + const { setItem } = useLocalStorage(); Object.defineProperty(window, 'matchMedia', { writable: true, - value: jest.fn().mockImplementation((query) => ({ + value: vi.fn().mockImplementation((query) => ({ matches: false, media: query, onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), + addListener: vi.fn(), // Deprecated + removeListener: vi.fn(), // Deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), })), }); let mockID: string | undefined = '123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockID }), -})); + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: mockID }), + }; +}); const MOCKS = [ { @@ -86,7 +91,7 @@ const clickToggleMenuBtn = (toggleButton: HTMLElement): void => { }; describe('Testing LeftDrawer in OrganizationScreen', () => { - test('should be redirected to / if IsLoggedIn is false', async () => { + it('should be redirected to / if IsLoggedIn is false', async () => { setItem('IsLoggedIn', false); render( @@ -101,7 +106,7 @@ describe('Testing LeftDrawer in OrganizationScreen', () => { ); expect(window.location.pathname).toEqual('/'); }); - test('should be redirected to / if ss is false', async () => { + it('should be redirected to / if ss is false', async () => { setItem('IsLoggedIn', true); render( @@ -115,7 +120,7 @@ describe('Testing LeftDrawer in OrganizationScreen', () => { , ); }); - test('Testing LeftDrawer in page functionality', async () => { + it('Testing LeftDrawer in page functionality', async () => { setItem('IsLoggedIn', true); setItem('AdminFor', [ { _id: '6637904485008f171cf29924', __typename: 'Organization' }, @@ -148,7 +153,7 @@ describe('Testing LeftDrawer in OrganizationScreen', () => { expect(icon).toHaveClass('fa fa-angle-double-right'); }); - test('should be redirected to / if orgId is undefined', async () => { + it('should be redirected to / if orgId is undefined', async () => { mockID = undefined; render( diff --git a/src/components/EventListCard/EventListCard.test.tsx b/src/components/EventListCard/EventListCard.spec.tsx similarity index 92% rename from src/components/EventListCard/EventListCard.test.tsx rename to src/components/EventListCard/EventListCard.spec.tsx index afe81f436e..e0c6cc825f 100644 --- a/src/components/EventListCard/EventListCard.test.tsx +++ b/src/components/EventListCard/EventListCard.spec.tsx @@ -17,16 +17,17 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import useLocalStorage from 'utils/useLocalstorage'; import { props } from './EventListCardProps'; import { ERROR_MOCKS, MOCKS } from './EventListCardMocks'; +import { vi, beforeAll, afterAll, expect, it } from 'vitest'; const { setItem } = useLocalStorage(); const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(ERROR_MOCKS, true); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -101,18 +102,17 @@ describe('Testing Event List Card', () => { }; beforeAll(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId' }), + vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); }); afterAll(() => { localStorage.clear(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('Testing for event modal', async () => { + it('Testing for event modal', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -129,7 +129,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should navigate to "/" if orgId is not defined', async () => { + it('Should navigate to "/" if orgId is not defined', async () => { render( @@ -163,7 +163,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render default text if event details are null', async () => { + it('Should render default text if event details are null', async () => { renderEventListCard(props[0]); await waitFor(() => { @@ -171,7 +171,7 @@ describe('Testing Event List Card', () => { }); }); - test('should render props and text elements test for the screen', async () => { + it('should render props and text elements test for the screen', async () => { renderEventListCard(props[1]); expect(screen.getByText(props[1].eventName)).toBeInTheDocument(); @@ -198,7 +198,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render truncated event name when length is more than 100', async () => { + it('Should render truncated event name when length is more than 100', async () => { const longEventName = 'a'.repeat(101); renderEventListCard({ ...props[1], eventName: longEventName }); @@ -221,7 +221,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render full event name when length is less than or equal to 100', async () => { + it('Should render full event name when length is less than or equal to 100', async () => { const shortEventName = 'a'.repeat(100); renderEventListCard({ ...props[1], eventName: shortEventName }); @@ -242,7 +242,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render truncated event description when length is more than 256', async () => { + it('Should render truncated event description when length is more than 256', async () => { const longEventDescription = 'a'.repeat(257); renderEventListCard({ @@ -268,7 +268,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render full event description when length is less than or equal to 256', async () => { + it('Should render full event description when length is less than or equal to 256', async () => { const shortEventDescription = 'a'.repeat(256); renderEventListCard({ @@ -294,7 +294,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should navigate to event dashboard when clicked (For Admin)', async () => { + it('Should navigate to event dashboard when clicked (For Admin)', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -311,7 +311,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should navigate to event dashboard when clicked (For User)', async () => { + it('Should navigate to event dashboard when clicked (For User)', async () => { setItem('userId', '123'); renderEventListCard(props[2]); @@ -329,7 +329,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should update a non-recurring event', async () => { + it('Should update a non-recurring event', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -372,7 +372,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should update a non all day non-recurring event', async () => { + it('Should update a non all day non-recurring event', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -425,7 +425,7 @@ describe('Testing Event List Card', () => { }); }); - test('should update a single event to be recurring', async () => { + it('should update a single event to be recurring', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -469,7 +469,7 @@ describe('Testing Event List Card', () => { }); }); - test('should show different update options for a recurring event based on different conditions', async () => { + it('should show different update options for a recurring event based on different conditions', async () => { renderEventListCard(props[5]); userEvent.click(screen.getByTestId('card')); @@ -595,7 +595,7 @@ describe('Testing Event List Card', () => { }); }); - test('should show recurrenceRule as changed if the recurrence weekdays have changed', async () => { + it('should show recurrenceRule as changed if the recurrence weekdays have changed', async () => { renderEventListCard(props[4]); userEvent.click(screen.getByTestId('card')); @@ -656,7 +656,7 @@ describe('Testing Event List Card', () => { }); }); - test('should update all instances of a recurring event', async () => { + it('should update all instances of a recurring event', async () => { renderEventListCard(props[6]); userEvent.click(screen.getByTestId('card')); @@ -706,7 +706,7 @@ describe('Testing Event List Card', () => { }); }); - test('should update thisAndFollowingInstances of a recurring event', async () => { + it('should update thisAndFollowingInstances of a recurring event', async () => { renderEventListCard(props[5]); userEvent.click(screen.getByTestId('card')); @@ -772,7 +772,7 @@ describe('Testing Event List Card', () => { }); }); - test('should render the delete modal', async () => { + it('should render the delete modal', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -807,7 +807,7 @@ describe('Testing Event List Card', () => { }); }); - test('should call the delete event mutation when the "Yes" button is clicked', async () => { + it('should call the delete event mutation when the "Yes" button is clicked', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -833,7 +833,7 @@ describe('Testing Event List Card', () => { }); }); - test('select different delete options on recurring events & then delete the recurring event', async () => { + it('select different delete options on recurring events & then delete the recurring event', async () => { renderEventListCard(props[4]); await wait(); @@ -873,7 +873,7 @@ describe('Testing Event List Card', () => { }); }); - test('should show an error toast when the delete event mutation fails', async () => { + it('should show an error toast when the delete event mutation fails', async () => { // Destructure key from props[1] and pass it separately to avoid spreading it const { key, ...otherProps } = props[1]; render( @@ -908,7 +908,7 @@ describe('Testing Event List Card', () => { }); }); - test('handle register should work properly', async () => { + it('handle register should work properly', async () => { setItem('userId', '456'); renderEventListCard(props[2]); @@ -933,7 +933,7 @@ describe('Testing Event List Card', () => { }); }); - test('should show already registered text when the user is registered for an event', async () => { + it('should show already registered text when the user is registered for an event', async () => { renderEventListCard(props[3]); userEvent.click(screen.getByTestId('card')); diff --git a/src/components/EventListCard/EventListCardModals.tsx b/src/components/EventListCard/EventListCardModals.tsx index 193890941c..7755da2a5a 100644 --- a/src/components/EventListCard/EventListCardModals.tsx +++ b/src/components/EventListCard/EventListCardModals.tsx @@ -292,7 +292,6 @@ function EventListCardModals({ } } } catch (error: unknown) { - /* istanbul ignore next */ errorHandler(t, error); } }; @@ -362,7 +361,6 @@ function EventListCardModals({ hideViewModal(); } } catch (error: unknown) { - /* istanbul ignore next */ errorHandler(t, error); } } @@ -491,9 +489,7 @@ function EventListCardModals({ recurrenceStartDate: date?.toDate(), weekDays: [Days[date?.toDate().getDay()]], weekDayOccurenceInMonth: weekDayOccurenceInMonth - ? /* istanbul ignore next */ getWeekDayOccurenceInMonth( - date?.toDate(), - ) + ? getWeekDayOccurenceInMonth(date?.toDate()) : undefined, }); } @@ -531,8 +527,7 @@ function EventListCardModals({ endTime: timeToDayJs(formState.endTime) < time ? time?.format('HH:mm:ss') - : /* istanbul ignore next */ - formState.endTime, + : formState.endTime, }); } }} diff --git a/src/components/EventManagement/Dashboard/EventDashboard.module.css b/src/components/EventManagement/Dashboard/EventDashboard.module.css deleted file mode 100644 index 37336002bb..0000000000 --- a/src/components/EventManagement/Dashboard/EventDashboard.module.css +++ /dev/null @@ -1,101 +0,0 @@ -.eventContainer { - display: flex; - align-items: start; -} - -.eventDetailsBox { - position: relative; - box-sizing: border-box; - background: #ffffff; - width: 66%; - padding: 0.3rem; - box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); - border-radius: 20px; - margin-bottom: 0; - margin-top: 20px; -} -.ctacards { - padding: 20px; - width: 100%; - display: flex; - background-color: #ffffff; - margin: 0 4px; - justify-content: space-between; - box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); - align-items: center; - border-radius: 20px; -} -.ctacards span { - color: rgb(181, 181, 181); - font-size: small; -} -/* .eventDetailsBox::before { - content: ''; - position: absolute; - top: 0; - height: 100%; - width: 6px; - background-color: #31bb6b; - border-radius: 20px; -} */ - -.time { - display: flex; - justify-content: space-between; - padding: 15px; - padding-bottom: 0px; - width: 33%; - - box-sizing: border-box; - background: #ffffff; - box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); - border-radius: 20px; - margin-bottom: 0; - margin-top: 20px; - margin-left: 10px; -} - -.startTime, -.endTime { - display: flex; - font-size: 20px; -} - -.to { - padding-right: 10px; -} - -.startDate, -.endDate { - color: #808080; - font-size: 14px; -} - -.titlename { - font-weight: 600; - font-size: 25px; - padding: 15px; - padding-bottom: 0px; - width: 50%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.description { - color: #737373; - font-weight: 300; - font-size: 14px; - word-wrap: break-word; - padding: 15px; - padding-bottom: 0px; -} - -.toporgloc { - font-size: 16px; - padding: 0.5rem; -} - -.toporgloc span { - color: #737373; -} diff --git a/src/components/EventManagement/Dashboard/EventDashboard.test.tsx b/src/components/EventManagement/Dashboard/EventDashboard.spec.tsx similarity index 88% rename from src/components/EventManagement/Dashboard/EventDashboard.test.tsx rename to src/components/EventManagement/Dashboard/EventDashboard.spec.tsx index dc605a1604..672282ff4a 100644 --- a/src/components/EventManagement/Dashboard/EventDashboard.test.tsx +++ b/src/components/EventManagement/Dashboard/EventDashboard.spec.tsx @@ -13,6 +13,7 @@ import type { ApolloLink, DefaultOptions } from '@apollo/client'; import { MOCKS_WITHOUT_TIME, MOCKS_WITH_TIME } from './EventDashboard.mocks'; import { StaticMockLink } from 'utils/StaticMockLink'; +import { vi, expect, it, describe } from 'vitest'; const mockWithTime = new StaticMockLink(MOCKS_WITH_TIME, true); const mockWithoutTime = new StaticMockLink(MOCKS_WITHOUT_TIME, true); @@ -38,9 +39,8 @@ async function wait(ms = 500): Promise { } const mockID = 'event123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ eventId: mockID }), +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); const renderEventDashboard = (mockLink: ApolloLink): RenderResult => { @@ -63,7 +63,7 @@ const renderEventDashboard = (mockLink: ApolloLink): RenderResult => { }; describe('Testing Event Dashboard Screen', () => { - test('The page should display event details correctly and also show the time if provided', async () => { + it('The page should display event details correctly and also show the time if provided', async () => { const { getByTestId } = renderEventDashboard(mockWithTime); await wait(); @@ -84,7 +84,7 @@ describe('Testing Event Dashboard Screen', () => { fireEvent.click(closeButton); }); - test('The page should display event details correctly and should not show the time if it is null', async () => { + it('The page should display event details correctly and should not show the time if it is null', async () => { const { getByTestId } = renderEventDashboard(mockWithoutTime); await wait(); @@ -92,7 +92,7 @@ describe('Testing Event Dashboard Screen', () => { expect(getByTestId('event-time')).toBeInTheDocument(); }); - test('Should show loader while data is being fetched', async () => { + it('Should show loader while data is being fetched', async () => { const { getByTestId, queryByTestId } = renderEventDashboard(mockWithTime); expect(getByTestId('spinner')).toBeInTheDocument(); // Wait for loading to complete diff --git a/src/components/EventManagement/Dashboard/EventDashboard.tsx b/src/components/EventManagement/Dashboard/EventDashboard.tsx index d3552702c6..1995a640c9 100644 --- a/src/components/EventManagement/Dashboard/EventDashboard.tsx +++ b/src/components/EventManagement/Dashboard/EventDashboard.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { Col, Row } from 'react-bootstrap'; -import styles from './EventDashboard.module.css'; +import styles from '../../../style/app.module.css'; import { useTranslation } from 'react-i18next'; import { EVENT_DETAILS } from 'GraphQl/Queries/Queries'; import { useQuery } from '@apollo/client'; diff --git a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.module.css b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.module.css deleted file mode 100644 index 9d1c32b766..0000000000 --- a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.eventAgendaItemContainer h2 { - margin: 0.6rem 0; -} - -.btnsContainer { - display: flex; - gap: 10px; -} - -@media (max-width: 768px) { - .btnsContainer { - margin-bottom: 0; - display: flex; - flex-direction: column; - } - - .createAgendaItemButton { - position: absolute; - top: 1rem; - right: 2rem; - } -} diff --git a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.spec.tsx similarity index 87% rename from src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx rename to src/components/EventManagement/EventAgendaItems/EventAgendaItems.spec.tsx index 3bce7ad11e..fabd3312dd 100644 --- a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx +++ b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.spec.tsx @@ -7,9 +7,7 @@ import { waitForElementToBeRemoved, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -17,11 +15,10 @@ import i18n from 'utils/i18nForTest'; // import { toast } from 'react-toastify'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; - import EventAgendaItems from './EventAgendaItems'; +import { vi, describe, expect, it, beforeEach } from 'vitest'; import { MOCKS, @@ -29,21 +26,20 @@ import { // MOCKS_ERROR_MUTATION, } from './EventAgendaItemsMocks'; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ eventId: '123' }), +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); //temporarily fixes react-beautiful-dnd droppable method's depreciation error //needs to be fixed in React 19 -jest.spyOn(console, 'error').mockImplementation((message) => { +vi.spyOn(console, 'error').mockImplementation((message) => { if (message.includes('Support for defaultProps will be removed')) { return; } @@ -78,8 +74,18 @@ describe('Testing Agenda Items Components', () => { attachments: [], urls: [], }; - test('Component loads correctly', async () => { - window.location.assign('/event/111/123'); + + beforeEach(() => { + Object.defineProperty(window, 'location', { + configurable: true, + value: { + reload: vi.fn(), + href: 'https://localhost:4321/event/111/123', + }, + }); + }); + + it('Component loads correctly', async () => { const { getByText } = render( @@ -99,8 +105,7 @@ describe('Testing Agenda Items Components', () => { }); }); - test('render error component on unsuccessful agenda item query', async () => { - window.location.assign('/event/111/123'); + it('render error component on unsuccessful agenda item query', async () => { const { queryByText } = render( @@ -122,8 +127,7 @@ describe('Testing Agenda Items Components', () => { }); }); - test('opens and closes the create agenda item modal', async () => { - window.location.assign('/event/111/123'); + it('opens and closes the create agenda item modal', async () => { render( @@ -156,8 +160,7 @@ describe('Testing Agenda Items Components', () => { screen.queryByTestId('createAgendaItemModalCloseBtn'), ); }); - test('creates new agenda item', async () => { - window.location.assign('/event/111/123'); + it('creates new agenda item', async () => { render( diff --git a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.tsx b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.tsx index b49ade4626..9b758a555a 100644 --- a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.tsx +++ b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.tsx @@ -20,7 +20,7 @@ import type { import AgendaItemsContainer from 'components/AgendaItems/AgendaItemsContainer'; import AgendaItemsCreateModal from 'components/AgendaItems/AgendaItemsCreateModal'; -import styles from './EventAgendaItems.module.css'; +import styles from '../../../style/app.module.css'; import Loader from 'components/Loader/Loader'; /** diff --git a/src/components/EventManagement/EventAttendance/AttendedEventList.test.tsx b/src/components/EventManagement/EventAttendance/AttendedEventList.spec.tsx similarity index 92% rename from src/components/EventManagement/EventAttendance/AttendedEventList.test.tsx rename to src/components/EventManagement/EventAttendance/AttendedEventList.spec.tsx index 2d60081acf..b365ba89ff 100644 --- a/src/components/EventManagement/EventAttendance/AttendedEventList.test.tsx +++ b/src/components/EventManagement/EventAttendance/AttendedEventList.spec.tsx @@ -7,6 +7,7 @@ import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import { formatDate } from 'utils/dateFormatter'; +import { describe, expect, it } from 'vitest'; const mockEvent = { _id: 'event123', @@ -51,7 +52,7 @@ describe('Testing AttendedEventList', () => { eventId: 'event123', }; - test('Component renders and displays event details correctly', async () => { + it('Component renders and displays event details correctly', async () => { const { queryByText, queryByTitle } = render( @@ -71,7 +72,7 @@ describe('Testing AttendedEventList', () => { }); }); - test('Component handles error state gracefully', async () => { + it('Component handles error state gracefully', async () => { const errorMock = [ { request: { @@ -99,7 +100,7 @@ describe('Testing AttendedEventList', () => { }); }); - test('Component renders link with correct URL', async () => { + it('Component renders link with correct URL', async () => { const { container } = render( diff --git a/src/components/EventManagement/EventAttendance/EventAttendance.test.tsx b/src/components/EventManagement/EventAttendance/EventAttendance.spec.tsx similarity index 85% rename from src/components/EventManagement/EventAttendance/EventAttendance.test.tsx rename to src/components/EventManagement/EventAttendance/EventAttendance.spec.tsx index db44357d07..bff1553cc0 100644 --- a/src/components/EventManagement/EventAttendance/EventAttendance.test.tsx +++ b/src/components/EventManagement/EventAttendance/EventAttendance.spec.tsx @@ -17,6 +17,7 @@ import userEvent from '@testing-library/user-event'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18n from 'utils/i18nForTest'; import { MOCKS } from './Attendance.mocks'; +import { vi, describe, beforeEach, afterEach, expect, it } from 'vitest'; const link = new StaticMockLink(MOCKS, true); @@ -25,7 +26,7 @@ async function wait(): Promise { return Promise.resolve(); }); } -jest.mock('react-chartjs-2', () => ({ +vi.mock('react-chartjs-2', () => ({ Line: () => null, Bar: () => null, Pie: () => null, @@ -47,18 +48,17 @@ const renderEventAttendance = (): RenderResult => { describe('Event Attendance Component', () => { beforeEach(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ eventId: 'event123', orgId: 'org123' }), + vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); cleanup(); }); - test('Component loads correctly with table headers', async () => { + it('Component loads correctly with table headers', async () => { renderEventAttendance(); await wait(); @@ -70,7 +70,7 @@ describe('Event Attendance Component', () => { }); }); - test('Renders attendee data correctly', async () => { + it('Renders attendee data correctly', async () => { renderEventAttendance(); await wait(); @@ -83,7 +83,7 @@ describe('Event Attendance Component', () => { }); }); - test('Search filters attendees by name correctly', async () => { + it('Search filters attendees by name correctly', async () => { renderEventAttendance(); await wait(); @@ -97,7 +97,7 @@ describe('Event Attendance Component', () => { }); }); - test('Sort functionality changes attendee order', async () => { + it('Sort functionality changes attendee order', async () => { renderEventAttendance(); await wait(); @@ -112,7 +112,7 @@ describe('Event Attendance Component', () => { }); }); - test('Date filter shows correct number of attendees', async () => { + it('Date filter shows correct number of attendees', async () => { renderEventAttendance(); await wait(); @@ -124,7 +124,7 @@ describe('Event Attendance Component', () => { expect(screen.getByText('Attendees not Found')).toBeInTheDocument(); }); }); - test('Statistics modal opens and closes correctly', async () => { + it('Statistics modal opens and closes correctly', async () => { renderEventAttendance(); await wait(); diff --git a/src/components/EventManagement/EventAttendance/EventAttendance.tsx b/src/components/EventManagement/EventAttendance/EventAttendance.tsx index 17f063f6b5..06b047a973 100644 --- a/src/components/EventManagement/EventAttendance/EventAttendance.tsx +++ b/src/components/EventManagement/EventAttendance/EventAttendance.tsx @@ -16,7 +16,7 @@ import { Table, FormControl, } from 'react-bootstrap'; -import styles from './EventsAttendance.module.css'; +import styles from '../../../style/app.module.css'; import { useLazyQuery } from '@apollo/client'; import { EVENT_ATTENDEES } from 'GraphQl/Queries/Queries'; import { useParams, Link } from 'react-router-dom'; diff --git a/src/components/EventManagement/EventAttendance/EventStatistics.test.tsx b/src/components/EventManagement/EventAttendance/EventStatistics.spec.tsx similarity index 88% rename from src/components/EventManagement/EventAttendance/EventStatistics.test.tsx rename to src/components/EventManagement/EventAttendance/EventStatistics.spec.tsx index 03f4671a5e..38379b54fb 100644 --- a/src/components/EventManagement/EventAttendance/EventStatistics.test.tsx +++ b/src/components/EventManagement/EventAttendance/EventStatistics.spec.tsx @@ -5,21 +5,26 @@ import { MockedProvider } from '@apollo/client/testing'; import { EVENT_DETAILS, RECURRING_EVENTS } from 'GraphQl/Queries/Queries'; import userEvent from '@testing-library/user-event'; import { exportToCSV } from 'utils/chartToPdf'; +import { vi, describe, expect, it } from 'vitest'; +import type { Mock } from 'vitest'; // Mock chart.js to avoid canvas errors -jest.mock('react-chartjs-2', () => ({ +vi.mock('react-chartjs-2', async () => ({ + ...(await vi.importActual('react-chartjs-2')), Line: () => null, Bar: () => null, })); // Mock react-router-dom -jest.mock('react-router-dom', () => ({ +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), useParams: () => ({ orgId: 'org123', eventId: 'event123', }), })); -jest.mock('utils/chartToPdf', () => ({ - exportToCSV: jest.fn(), +vi.mock('utils/chartToPdf', async () => ({ + ...(await vi.importActual('utils/chartToPdf')), + exportToCSV: vi.fn(), })); const mocks = [ { @@ -139,7 +144,7 @@ const mockStatistics = { }; describe('AttendanceStatisticsModal', () => { - test('renders modal with correct initial state', async () => { + it('renders modal with correct initial state', async () => { render( { }); }); - test('switches between gender and age demographics', async () => { + it('switches between gender and age demographics', async () => { render( { }); }); - test('handles data demographics export functionality', async () => { - const mockExportToCSV = jest.fn(); - (exportToCSV as jest.Mock).mockImplementation(mockExportToCSV); + it('handles data demographics export functionality', async () => { + const mockExportToCSV = vi.fn(); + (exportToCSV as Mock).mockImplementation(mockExportToCSV); render( @@ -220,9 +225,9 @@ describe('AttendanceStatisticsModal', () => { expect(mockExportToCSV).toHaveBeenCalled(); }); - test('handles data trends export functionality', async () => { - const mockExportToCSV = jest.fn(); - (exportToCSV as jest.Mock).mockImplementation(mockExportToCSV); + it('handles data trends export functionality', async () => { + const mockExportToCSV = vi.fn(); + (exportToCSV as Mock).mockImplementation(mockExportToCSV); render( @@ -255,7 +260,7 @@ describe('AttendanceStatisticsModal', () => { expect(mockExportToCSV).toHaveBeenCalled(); }); - test('displays recurring event data correctly', async () => { + it('displays recurring event data correctly', async () => { render( { expect(screen.getByTestId('today-button')).toBeInTheDocument(); }); }); - test('handles pagination and today button correctly', async () => { + it('handles pagination and today button correctly', async () => { render( { expect(screen.getByTestId('today-button')).toBeInTheDocument(); }); - test('handles pagination in recurring events view', async () => { + it('handles pagination in recurring events view', async () => { render( { }); }); - test('closes modal correctly', async () => { - const handleClose = jest.fn(); + it('closes modal correctly', async () => { + const handleClose = vi.fn(); render( { switch (eventKey) { case 'trends': @@ -379,34 +377,25 @@ export const AttendanceStatisticsModal: React.FC< id="pdf-content" >
{isEventRecurring ? (

Trends

@@ -458,23 +447,13 @@ export const AttendanceStatisticsModal: React.FC<
) : (
-

+

{statistics.totalMembers}

Attendance Count

@@ -533,13 +512,7 @@ export const AttendanceStatisticsModal: React.FC< }} />

Demography

diff --git a/src/components/EventManagement/EventAttendance/EventsAttendance.module.css b/src/components/EventManagement/EventAttendance/EventsAttendance.module.css deleted file mode 100644 index 2ee236a4da..0000000000 --- a/src/components/EventManagement/EventAttendance/EventsAttendance.module.css +++ /dev/null @@ -1,35 +0,0 @@ -.input { - display: flex; - width: 100%; - position: relative; -} -.customcell { - background-color: #31bb6b !important; - color: white !important; - font-size: medium !important; - font-weight: 500 !important; - padding-top: 10px !important; - padding-bottom: 10px !important; -} - -.eventsAttended, -.membername { - color: blue; -} -.actionBtn { - /* color:#39a440 !important; */ - background-color: #ffffff !important; -} -.actionBtn:hover, -.actionBtn:focus, -.actionBtn:active { - color: #39a440 !important; -} - -.table-body > .table-row { - background-color: #fff !important; -} - -.table-body > .table-row:nth-child(2n) { - background: #afffe8 !important; -} diff --git a/src/components/EventManagement/EventRegistrant/EventRegistrants.spec.tsx b/src/components/EventManagement/EventRegistrant/EventRegistrants.spec.tsx new file mode 100644 index 0000000000..828362706a --- /dev/null +++ b/src/components/EventManagement/EventRegistrant/EventRegistrants.spec.tsx @@ -0,0 +1,264 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import type { RenderResult } from '@testing-library/react'; +import { render, screen, cleanup, waitFor } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { I18nextProvider } from 'react-i18next'; +import EventRegistrants from './EventRegistrants'; +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import i18n from 'utils/i18nForTest'; +import { REGISTRANTS_MOCKS } from './Registrations.mocks'; +import { MOCKS as ATTENDEES_MOCKS } from '../EventAttendance/Attendance.mocks'; +import { vi } from 'vitest'; +import { EVENT_REGISTRANTS, EVENT_ATTENDEES } from 'GraphQl/Queries/Queries'; + +const COMBINED_MOCKS = [...REGISTRANTS_MOCKS, ...ATTENDEES_MOCKS]; + +const link = new StaticMockLink(COMBINED_MOCKS, true); + +async function wait(): Promise { + await waitFor(() => { + return Promise.resolve(); + }); +} + +const renderEventRegistrants = (): RenderResult => { + return render( + + + + + + + + + , + ); +}; + +describe('Event Registrants Component', () => { + beforeEach(() => { + vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ eventId: 'event123', orgId: 'org123' }), + useNavigate: vi.fn(), + }; + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + cleanup(); + }); + + test('Component loads correctly with table headers', async () => { + renderEventRegistrants(); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('table-header-serial')).toBeInTheDocument(); + expect(screen.getByTestId('table-header-registrant')).toBeInTheDocument(); + expect(screen.getByTestId('table-header-created-at')).toBeInTheDocument(); + expect( + screen.getByTestId('table-header-add-registrant'), + ).toBeInTheDocument(); + }); + }); + + test('Renders registrants button correctly', async () => { + renderEventRegistrants(); + + await waitFor(() => { + expect(screen.getByTestId('stats-modal')).toBeInTheDocument(); + expect(screen.getByTestId('filter-button')).toBeInTheDocument(); + }); + }); + + test('Handles empty registrants list', async () => { + const emptyMocks = [ + { + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: '660fdf7d2c1ef6c7db1649ad' }, + }, + result: { + data: { + getEventAttendeesByEventId: [], + }, + }, + }, + { + request: { + query: EVENT_ATTENDEES, + variables: { id: '660fdf7d2c1ef6c7db1649ad' }, + }, + result: { + data: { + event: { + attendees: [], + }, + }, + }, + }, + ]; + + const customLink = new StaticMockLink(emptyMocks, true); + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('no-registrants')).toBeInTheDocument(); + }); + }); + + test('Successfully combines and displays registrant and attendee data', async () => { + const mockData = [ + { + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: 'event123' }, + }, + result: { + data: { + getEventAttendeesByEventId: [ + { + _id: '1', + userId: 'user1', + isRegistered: true, + __typename: 'EventAttendee', + }, + ], + }, + }, + }, + { + request: { + query: EVENT_ATTENDEES, + variables: { id: 'event123' }, + }, + result: { + data: { + event: { + attendees: [ + { + _id: 'user1', + firstName: 'John', + lastName: 'Doe', + createdAt: '2023-09-25T10:00:00.000Z', + __typename: 'User', + }, + ], + }, + }, + }, + }, + ]; + + const customLink = new StaticMockLink(mockData, true); + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('registrant-row-0')).toBeInTheDocument(); + }); + + // Validate mapped data + expect(screen.getByTestId('attendee-name-0')).toHaveTextContent('John Doe'); + expect(screen.getByTestId('registrant-registered-at-0')).toHaveTextContent( + '2023-09-25', + ); + expect(screen.getByTestId('registrant-created-at-0')).toHaveTextContent( + '10:00:00', + ); + }); + + test('Handles missing attendee data with fallback values', async () => { + const mocksWithMissingFields = [ + { + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: 'event123' }, + }, + result: { + data: { + getEventAttendeesByEventId: [ + { + _id: '1', + userId: 'user1', + isRegistered: true, + __typename: 'EventAttendee', + }, + ], + }, + }, + }, + { + request: { + query: EVENT_ATTENDEES, + variables: { id: 'event123' }, + }, + result: { + data: { + event: { + attendees: [ + { + _id: 'user1', + firstName: 'Jane', + lastName: 'Doe', + createdAt: null, + __typename: 'User', + }, + ], + }, + }, + }, + }, + ]; + + const customLink = new StaticMockLink(mocksWithMissingFields, true); + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('registrant-row-0')).toBeInTheDocument(); + }); + + // Validate fallback values + expect(screen.getByTestId('attendee-name-0')).toHaveTextContent('Jane Doe'); + expect(screen.getByTestId('registrant-created-at-0')).toHaveTextContent( + 'N/A', + ); + }); +}); diff --git a/src/components/EventManagement/EventRegistrant/EventRegistrants.tsx b/src/components/EventManagement/EventRegistrant/EventRegistrants.tsx new file mode 100644 index 0000000000..efcc2e91f7 --- /dev/null +++ b/src/components/EventManagement/EventRegistrant/EventRegistrants.tsx @@ -0,0 +1,227 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { + Paper, + TableCell, + TableContainer, + TableHead, + TableRow, + TableBody, +} from '@mui/material'; +import { Button, Table } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import style from '../../../style/app.module.css'; +import { useLazyQuery } from '@apollo/client'; +import { EVENT_ATTENDEES, EVENT_REGISTRANTS } from 'GraphQl/Queries/Queries'; +import { useParams } from 'react-router-dom'; +import type { InterfaceMember } from '../EventAttendance/InterfaceEvents'; +import { EventRegistrantsWrapper } from 'components/EventRegistrantsModal/EventRegistrantsWrapper'; +import { CheckInWrapper } from 'components/CheckIn/CheckInWrapper'; +/** + * Interface for user data + */ +interface InterfaceUser { + _id: string; + userId: string; + isRegistered: boolean; + __typename: string; + time: string; +} +/** + * Component to manage and display event registrant information + * Includes adding new registrants and check-in functionality for registrants + * @returns JSX element containing the event attendance interface + */ +function EventRegistrants(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'eventRegistrant', + }); + const { orgId, eventId } = useParams<{ orgId: string; eventId: string }>(); + const [registrants, setRegistrants] = useState([]); + const [attendees, setAttendees] = useState([]); + const [combinedData, setCombinedData] = useState< + (InterfaceUser & Partial)[] + >([]); + // Fetch registrants + const [getEventRegistrants] = useLazyQuery(EVENT_REGISTRANTS, { + variables: { eventId }, + fetchPolicy: 'cache-and-network', + onCompleted: (data) => { + if (data?.getEventAttendeesByEventId) { + const mappedData = data.getEventAttendeesByEventId.map( + (attendee: InterfaceUser) => ({ + _id: attendee._id, + userId: attendee.userId, + isRegistered: attendee.isRegistered, + __typename: attendee.__typename, + }), + ); + setRegistrants(mappedData); + } + }, + }); + // Fetch attendees + const [getEventAttendees] = useLazyQuery(EVENT_ATTENDEES, { + variables: { id: eventId }, + fetchPolicy: 'cache-and-network', + onCompleted: (data) => { + if (data?.event?.attendees) { + setAttendees(data.event.attendees); + } + }, + }); + // callback function to refresh the data + const refreshData = useCallback(() => { + getEventRegistrants(); + getEventAttendees(); + }, [getEventRegistrants, getEventAttendees]); + useEffect(() => { + refreshData(); + }, [refreshData]); + // Combine registrants and attendees data + useEffect(() => { + if (registrants.length > 0 && attendees.length > 0) { + const mergedData = registrants.map((registrant) => { + const matchedAttendee = attendees.find( + (attendee) => attendee._id === registrant.userId, + ); + const [date, timeWithMilliseconds] = matchedAttendee?.createdAt + ? matchedAttendee.createdAt.split('T') + : ['N/A', 'N/A']; + const [time] = + timeWithMilliseconds !== 'N/A' + ? timeWithMilliseconds.split('.') + : ['N/A']; + return { + ...registrant, + firstName: matchedAttendee?.firstName || 'N/A', + lastName: matchedAttendee?.lastName || 'N/A', + createdAt: date, + time: time, + }; + }); + setCombinedData(mergedData); + } + }, [registrants, attendees]); + return ( +
+
+ {eventId ? ( + + ) : ( + + )} + +
+ + + + + + {t('serialNumber')} + + + {t('registrant')} + + + {t('registeredAt')} + + + {t('createdAt')} + + + {t('addRegistrant')} + + + + + {combinedData.length === 0 ? ( + + + {t('noRegistrantsFound')} + + + ) : ( + combinedData.map((data, index) => ( + + + {index + 1} + + + {data.firstName} {data.lastName} + + + {data.createdAt} + + + {data.time} + + + {eventId && orgId && ( + + )} + + + )) + )} + +
+
+
+ ); +} +export default EventRegistrants; diff --git a/src/components/EventManagement/EventRegistrant/Registrations.mocks.ts b/src/components/EventManagement/EventRegistrant/Registrations.mocks.ts new file mode 100644 index 0000000000..d71714459e --- /dev/null +++ b/src/components/EventManagement/EventRegistrant/Registrations.mocks.ts @@ -0,0 +1,67 @@ +import { EVENT_REGISTRANTS, EVENT_ATTENDEES } from 'GraphQl/Queries/Queries'; + +export const REGISTRANTS_MOCKS = [ + { + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: '660fdf7d2c1ef6c7db1649ad' }, + }, + result: { + data: { + getEventAttendeesByEventId: [ + { + _id: '6589386a2caa9d8d69087484', + userId: '6589386a2caa9d8d69087484', + isRegistered: true, + __typename: 'EventAttendee', + }, + { + _id: '6589386a2caa9d8d69087485', + userId: '6589386a2caa9d8d69087485', + isRegistered: true, + __typename: 'EventAttendee', + }, + ], + }, + }, + }, + { + request: { + query: EVENT_ATTENDEES, + variables: { id: '660fdf7d2c1ef6c7db1649ad' }, + }, + result: { + data: { + event: { + attendees: [ + { + _id: '6589386a2caa9d8d69087484', + firstName: 'Bruce', + lastName: 'Garza', + createdAt: '2023-04-13T10:23:17.742Z', + __typename: 'User', + }, + { + _id: '6589386a2caa9d8d69087485', + firstName: 'Jane', + lastName: 'Smith', + createdAt: '2023-04-13T10:23:17.742Z', + __typename: 'User', + }, + ], + }, + }, + }, + }, +]; + +export const REGISTRANTS_MOCKS_ERROR = [ + { + // Error mock for EVENT_REGISTRANTS query + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: 'event123' }, + }, + error: new Error('An error occurred while fetching registrants'), + }, +]; diff --git a/src/components/EventRegistrantsModal/AddOnSpotAttendee.test.tsx b/src/components/EventRegistrantsModal/AddOnSpotAttendee.spec.tsx similarity index 92% rename from src/components/EventRegistrantsModal/AddOnSpotAttendee.test.tsx rename to src/components/EventRegistrantsModal/AddOnSpotAttendee.spec.tsx index c0dc20d200..66f0dda38b 100644 --- a/src/components/EventRegistrantsModal/AddOnSpotAttendee.test.tsx +++ b/src/components/EventRegistrantsModal/AddOnSpotAttendee.spec.tsx @@ -11,23 +11,27 @@ import { Provider } from 'react-redux'; import { I18nextProvider } from 'react-i18next'; import { store } from 'state/store'; import i18nForTest from '../../utils/i18nForTest'; +import { describe, expect, vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); const mockProps = { show: true, - handleClose: jest.fn(), - reloadMembers: jest.fn(), + handleClose: vi.fn(), + reloadMembers: vi.fn(), }; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ eventId: '123', orgId: '123' }), -})); +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ eventId: '123', orgId: '123' }), + }; +}); const MOCKS = [ { @@ -80,7 +84,7 @@ const renderAddOnSpotAttendee = (): RenderResult => { describe('AddOnSpotAttendee Component', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('renders the component with all form fields', async () => { diff --git a/src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx b/src/components/EventRegistrantsModal/EventRegistrantsModal.spec.tsx similarity index 99% rename from src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx rename to src/components/EventRegistrantsModal/EventRegistrantsModal.spec.tsx index 8ca76393cd..4f422ceb7f 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsModal.spec.tsx @@ -15,6 +15,7 @@ import i18nForTest from 'utils/i18nForTest'; import { ToastContainer } from 'react-toastify'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { describe, test, expect, vi } from 'vitest'; const queryMockWithoutRegistrant = [ { @@ -160,7 +161,7 @@ describe('Testing Event Registrants Modal', () => { show: true, eventId: 'event123', orgId: 'org123', - handleClose: jest.fn(), + handleClose: vi.fn(), }; test('The modal should be rendered, correct text must be displayed when there are no attendees and add attendee mutation must function properly', async () => { diff --git a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css deleted file mode 100644 index 59b31333af..0000000000 --- a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css +++ /dev/null @@ -1,13 +0,0 @@ -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.test.tsx b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.spec.tsx similarity index 95% rename from src/components/EventRegistrantsModal/EventRegistrantsWrapper.test.tsx rename to src/components/EventRegistrantsModal/EventRegistrantsWrapper.spec.tsx index d1707e8520..97a0d1f00f 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.test.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.spec.tsx @@ -11,6 +11,7 @@ import i18nForTest from 'utils/i18nForTest'; import { ToastContainer } from 'react-toastify'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { describe, test, expect } from 'vitest'; const queryMock = [ { @@ -77,7 +78,7 @@ describe('Testing Event Registrants Wrapper', () => { ); // Open the modal - fireEvent.click(queryByText('Show Registrants') as Element); + fireEvent.click(queryByText('Add Registrants') as Element); await waitFor(() => expect(queryByText('Event Registrants')).toBeInTheDocument(), diff --git a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx index b198fcdd6d..36b6679a9f 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx @@ -1,13 +1,13 @@ 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'; +import style from '../../style/app.module.css'; // Props for the EventRegistrantsWrapper component type PropType = { eventId: string; orgId: string; + onUpdate?: () => void; }; /** @@ -20,37 +20,37 @@ type PropType = { export const EventRegistrantsWrapper = ({ eventId, orgId, + onUpdate, }: PropType): JSX.Element => { // State to control the visibility of the modal const [showModal, setShowModal] = useState(false); + const handleClose = (): void => { + setShowModal(false); + // Call onUpdate after modal is closed + if (onUpdate) { + onUpdate(); + } + }; return ( <> {/* Button to open the event registrants modal */} {/* Render the EventRegistrantsModal if showModal is true */} {showModal && ( { - setShowModal(false); // Hide the modal when closed - }} + handleClose={handleClose} eventId={eventId} orgId={orgId} /> diff --git a/src/components/EventStats/EventStats.test.tsx b/src/components/EventStats/EventStats.spec.tsx similarity index 75% rename from src/components/EventStats/EventStats.test.tsx rename to src/components/EventStats/EventStats.spec.tsx index e2496fa5af..058913c6e7 100644 --- a/src/components/EventStats/EventStats.test.tsx +++ b/src/components/EventStats/EventStats.spec.tsx @@ -4,13 +4,15 @@ import { MockedProvider } from '@apollo/react-testing'; import { EventStats } from './EventStats'; import { BrowserRouter } from 'react-router-dom'; import { EVENT_FEEDBACKS } from 'GraphQl/Queries/Queries'; +import { vi, describe, expect, it } from 'vitest'; -// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Vitest) // These modules are used by the Feedback component -jest.mock('@mui/x-charts/PieChart', () => ({ - pieArcLabelClasses: jest.fn(), - PieChart: jest.fn().mockImplementation(() => <>Test), - pieArcClasses: jest.fn(), +vi.mock('@mui/x-charts/PieChart', async () => ({ + ...(await vi.importActual('@mui/x-charts/PieChart')), + pieArcLabelClasses: vi.fn(), + PieChart: vi.fn().mockImplementation(() => <>Test), + pieArcClasses: vi.fn(), })); const mockData = [ @@ -43,10 +45,10 @@ describe('Testing Event Stats', () => { const props = { eventId: 'eventStats123', show: true, - handleClose: jest.fn(), + handleClose: vi.fn(), }; - test('The stats should be rendered properly', async () => { + it('The stats should be rendered properly', async () => { const { queryByText } = render( diff --git a/src/components/EventStats/EventStatsWrapper.test.tsx b/src/components/EventStats/EventStatsWrapper.spec.tsx similarity index 73% rename from src/components/EventStats/EventStatsWrapper.test.tsx rename to src/components/EventStats/EventStatsWrapper.spec.tsx index 0e64ac13cc..7a94aea5ef 100644 --- a/src/components/EventStats/EventStatsWrapper.test.tsx +++ b/src/components/EventStats/EventStatsWrapper.spec.tsx @@ -4,12 +4,15 @@ import { MockedProvider } from '@apollo/react-testing'; import { EventStatsWrapper } from './EventStatsWrapper'; import { BrowserRouter } from 'react-router-dom'; import { EVENT_FEEDBACKS } from 'GraphQl/Queries/Queries'; +import { vi, describe, expect, it } from 'vitest'; -// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) -jest.mock('@mui/x-charts/PieChart', () => ({ - pieArcLabelClasses: jest.fn(), - PieChart: jest.fn().mockImplementation(() => <>Test), - pieArcClasses: jest.fn(), +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Vitest) +// These modules are used by the Feedback component +vi.mock('@mui/x-charts/PieChart', async () => ({ + ...(await vi.importActual('@mui/x-charts/PieChart')), + pieArcLabelClasses: vi.fn(), + PieChart: vi.fn().mockImplementation(() => <>Test), + pieArcClasses: vi.fn(), })); const mockData = [ @@ -38,20 +41,12 @@ const mockData = [ }, ]; -// 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 component -jest.mock('@mui/x-charts/PieChart', () => ({ - pieArcLabelClasses: jest.fn(), - PieChart: jest.fn().mockImplementation(() => <>Test), - pieArcClasses: jest.fn(), -})); - describe('Testing Event Stats Wrapper', () => { const props = { eventId: 'eventStats123', }; - test('The button to open and close the modal should work properly', async () => { + it('The button to open and close the modal should work properly', async () => { const { queryByText, queryByRole } = render( diff --git a/src/components/EventStats/Statistics/AverageRating.test.tsx b/src/components/EventStats/Statistics/AverageRating.spec.tsx similarity index 91% rename from src/components/EventStats/Statistics/AverageRating.test.tsx rename to src/components/EventStats/Statistics/AverageRating.spec.tsx index 01cf6461e3..c5a6b91925 100644 --- a/src/components/EventStats/Statistics/AverageRating.test.tsx +++ b/src/components/EventStats/Statistics/AverageRating.spec.tsx @@ -7,6 +7,7 @@ import { store } from 'state/store'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import { ToastContainer } from 'react-toastify'; +import { describe, expect, it } from 'vitest'; const props = { data: { @@ -35,7 +36,7 @@ const props = { }; describe('Testing Average Rating Card', () => { - test('The component should be rendered and the Score should be shown', async () => { + it('The component should be rendered and the Score should be shown', async () => { const { queryByText } = render( diff --git a/src/components/EventStats/Statistics/Feedback.test.tsx b/src/components/EventStats/Statistics/Feedback.spec.tsx similarity index 81% rename from src/components/EventStats/Statistics/Feedback.test.tsx rename to src/components/EventStats/Statistics/Feedback.spec.tsx index 9abdee4c57..4fb020a70e 100644 --- a/src/components/EventStats/Statistics/Feedback.test.tsx +++ b/src/components/EventStats/Statistics/Feedback.spec.tsx @@ -7,12 +7,14 @@ import { store } from 'state/store'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import { ToastContainer } from 'react-toastify'; +import { vi, describe, expect, it } from 'vitest'; -// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Jest) -jest.mock('@mui/x-charts/PieChart', () => ({ - pieArcLabelClasses: jest.fn(), - PieChart: jest.fn().mockImplementation(() => <>Test), - pieArcClasses: jest.fn(), +// Mock the modules for PieChart rendering as they require a trasformer being used (which is not done by Vitest) +vi.mock('@mui/x-charts/PieChart', async () => ({ + ...(await vi.importActual('@mui/x-charts/PieChart')), + pieArcLabelClasses: vi.fn(), + PieChart: vi.fn().mockImplementation(() => <>Test), + pieArcClasses: vi.fn(), })); const nonEmptyProps = { @@ -52,7 +54,7 @@ const emptyProps = { }; describe('Testing Feedback Statistics Card', () => { - test('The component should be rendered and the feedback should be shown if present', async () => { + it('The component should be rendered and the feedback should be shown if present', async () => { const { queryByText } = render( @@ -79,7 +81,7 @@ describe('Testing Feedback Statistics Card', () => { }); }); - test('The component should be rendered and message should be shown if no feedback is present', async () => { + it('The component should be rendered and message should be shown if no feedback is present', async () => { const { queryByText } = render( diff --git a/src/components/EventStats/Statistics/Review.test.tsx b/src/components/EventStats/Statistics/Review.spec.tsx similarity index 89% rename from src/components/EventStats/Statistics/Review.test.tsx rename to src/components/EventStats/Statistics/Review.spec.tsx index 9093444ab2..5777c32c27 100644 --- a/src/components/EventStats/Statistics/Review.test.tsx +++ b/src/components/EventStats/Statistics/Review.spec.tsx @@ -7,6 +7,7 @@ import { store } from 'state/store'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import { ToastContainer } from 'react-toastify'; +import { describe, expect, it } from 'vitest'; const nonEmptyReviewProps = { data: { @@ -51,7 +52,7 @@ const emptyReviewProps = { }; describe('Testing Review Statistics Card', () => { - test('The component should be rendered and the reviews should be shown if present', async () => { + it('The component should be rendered and the reviews should be shown if present', async () => { const { queryByText } = render( @@ -72,7 +73,7 @@ describe('Testing Review Statistics Card', () => { await waitFor(() => expect(queryByText('review2')).toBeInTheDocument()); }); - test('The component should be rendered and message should be shown if no review is present', async () => { + it('The component should be rendered and message should be shown if no review is present', async () => { const { queryByText } = render( diff --git a/src/components/GroupChatDetails/GroupChatDetails.module.css b/src/components/GroupChatDetails/GroupChatDetails.module.css new file mode 100644 index 0000000000..61119875f0 --- /dev/null +++ b/src/components/GroupChatDetails/GroupChatDetails.module.css @@ -0,0 +1,94 @@ +.groupInfo { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.memberList { + max-height: 300px; + overflow: scroll; +} + +.memberList::-webkit-scrollbar { + display: none; +} + +.listItem { + display: flex; + align-items: center; + gap: 10px; +} + +.groupMembersList { + display: flex; + align-items: center; + justify-content: space-between; +} + +.groupMembersList p { + margin: 0; + color: #959595; +} + +.membersImage { + width: 40px !important; +} + +.groupImage { + margin-bottom: 10px; +} + +.editImgBtn { + padding: 2px 6px 6px 8px; + border-radius: 100%; + background-color: white; + border: 1px solid #959595; + color: #959595; + outline: none; + position: relative; + top: -40px; + left: 40px; +} + +.chatImage { + height: 120px; + border-radius: 100%; + width: 120px; +} + +.editChatNameContainer { + display: flex; + gap: 15px; + align-items: center; + font-size: 20px; + margin-bottom: 10px; +} + +.editChatNameContainer input { + border: none; + border-bottom: 1px solid rgb(171, 171, 171); + outline: none; + padding: 0px 5px; +} + +.editChatNameContainer h3 { + margin: 0; +} + +.cancelIcon { + color: rgb(197, 42, 42); + cursor: pointer; + font-size: 16px; +} + +.checkIcon { + color: rgb(42, 197, 42); + cursor: pointer; +} + +.chatUserDetails { + display: flex; + gap: 10px; + align-items: center; +} diff --git a/src/components/GroupChatDetails/GroupChatDetails.test.tsx b/src/components/GroupChatDetails/GroupChatDetails.test.tsx new file mode 100644 index 0000000000..4a4aa73b61 --- /dev/null +++ b/src/components/GroupChatDetails/GroupChatDetails.test.tsx @@ -0,0 +1,603 @@ +import React from 'react'; +import { + render, + screen, + // fireEvent, + // waitFor, + act, + fireEvent, + waitFor, +} from '@testing-library/react'; +import GroupChatDetails from './GroupChatDetails'; +import { MockedProvider } from '@apollo/client/testing'; +import { + ADD_USER_TO_GROUP_CHAT, + UPDATE_CHAT, +} from 'GraphQl/Mutations/OrganizationMutations'; +import { USERS_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; +import { I18nextProvider, initReactI18next } from 'react-i18next'; +import i18n from 'i18next'; +import { useLocalStorage } from 'utils/useLocalstorage'; + +const { setItem } = useLocalStorage(); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +i18n.use(initReactI18next).init({ + lng: 'en', + resources: { + en: { + translation: { + // Add your translations here + }, + }, + }, +}); + +const mockChat = { + _id: '1', + isGroup: true, + name: 'Test Group', + image: '', + messages: [], + admins: [ + { + _id: 'hbjguyt7y9890i9otyugttiyuh', + firstName: 'Admin', + lastName: 'User', + email: 'admin@example.com', + }, + ], + users: [ + { + _id: 'hbjguyt7y9890i9otyugttiyuh', + firstName: 'Admin', + lastName: 'User', + email: 'admin@example.com', + }, + { + _id: 'gjhnbbjku68979089ujhvserty', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + }, + ], + unseenMessagesByUsers: JSON.parse( + '{"hbjguyt7y9890i9otyugttiyuh": 0, "gjhnbbjku68979089ujhvserty": 0}', + ), + description: 'Test Description', +}; + +const mocks = [ + { + request: { + query: USERS_CONNECTION_LIST, + variables: { + firstName_contains: '', + lastName_contains: '', + }, + }, + result: { + data: { + users: [ + { + user: { + firstName: 'Deanne', + lastName: 'Marks', + image: null, + _id: '6589389d2caa9d8d69087487', + email: 'testuser8@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + organizationsBlockedBy: [], + joinedOrganizations: [ + { + _id: '6537904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Queens', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Coffee Street', + line2: 'Apartment 501', + postalCode: '11427', + sortingCode: 'ABC-133', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6637904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Staten Island', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 church Street', + line2: 'Apartment 499', + postalCode: '10301', + sortingCode: 'ABC-122', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6737904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Brooklyn', + countryCode: 'US', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Main Street', + line2: 'Apt 456', + postalCode: '10004', + sortingCode: 'ABC-789', + state: 'NY', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6437904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Bronx', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Random Street', + line2: 'Apartment 456', + postalCode: '10451', + sortingCode: 'ABC-123', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + ], + __typename: 'User', + }, + appUserProfile: { + _id: '64378abd85308f171cf2993d', + adminFor: [], + isSuperAdmin: false, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + __typename: 'AppUserProfile', + }, + __typename: 'UserData', + }, + ], + }, + }, + }, + { + request: { + query: USERS_CONNECTION_LIST, + variables: { + firstName_contains: 'Disha', + lastName_contains: '', + }, + }, + result: { + data: { + users: [ + { + user: { + firstName: 'Deanne', + lastName: 'Marks', + image: null, + _id: '6589389d2caa9d8d69087487', + email: 'testuser8@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + organizationsBlockedBy: [], + joinedOrganizations: [ + { + _id: '6537904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Queens', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Coffee Street', + line2: 'Apartment 501', + postalCode: '11427', + sortingCode: 'ABC-133', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6637904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Staten Island', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 church Street', + line2: 'Apartment 499', + postalCode: '10301', + sortingCode: 'ABC-122', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6737904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Brooklyn', + countryCode: 'US', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Main Street', + line2: 'Apt 456', + postalCode: '10004', + sortingCode: 'ABC-789', + state: 'NY', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6437904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Bronx', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Random Street', + line2: 'Apartment 456', + postalCode: '10451', + sortingCode: 'ABC-123', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + ], + __typename: 'User', + }, + appUserProfile: { + _id: '64378abd85308f171cf2993d', + adminFor: [], + isSuperAdmin: false, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + __typename: 'AppUserProfile', + }, + __typename: 'UserData', + }, + ], + }, + }, + }, + { + request: { + query: ADD_USER_TO_GROUP_CHAT, + variables: { userId: '6589389d2caa9d8d69087487', chatId: '1' }, + }, + result: { + data: { + addUserToGroupChat: { + _id: '1', + success: true, + }, + }, + }, + }, + { + request: { + query: UPDATE_CHAT, + variables: { input: { _id: '1', image: '', name: 'New Group name' } }, + }, + result: { + data: { + updateChat: { + _id: '1', + success: true, + }, + }, + }, + }, + { + request: { + query: UPDATE_CHAT, + variables: {}, + }, + result: { + data: { + updateChat: { + _id: '1', + success: true, + }, + }, + }, + }, +]; + +describe('GroupChatDetails', () => { + it('renders correctly', async () => { + const chat = { + _id: '1', + isGroup: true, + name: 'Test Group', + image: '', + messages: [], + admins: [], + users: [], + unseenMessagesByUsers: JSON.parse( + '{"hbjguyt7y9890i9otyugttiyuh": 0, "gjhnbbjku68979089ujhvserty": 0}', + ), + description: 'Test Description', + }; + render( + + + + + , + ); + + await wait(); + + expect(screen.getByText('Test Group')).toBeInTheDocument(); + expect(screen.getByText('Test Description')).toBeInTheDocument(); + const closeButton = screen.getByRole('button', { name: /close/i }); + expect(closeButton).toBeInTheDocument(); + + fireEvent.click(closeButton); + }); + + it('edit chat title', async () => { + render( + + + + + , + ); + + await wait(); + await act(async () => { + fireEvent.click(await screen.findByTestId('editTitleBtn')); + }); + + await waitFor(async () => { + expect(await screen.findByTestId('chatNameInput')).toBeInTheDocument(); + }); + await act(async () => { + fireEvent.change(await screen.findByTestId('chatNameInput'), { + target: { value: 'New Group name' }, + }); + }); + + await act(async () => { + fireEvent.click(await screen.findByTestId('updateTitleBtn')); + }); + + await wait(); + + await waitFor( + async () => { + expect(await screen.findByTestId('editTitleBtn')).toBeInTheDocument(); + }, + { timeout: 5000 }, + ); + + await act(async () => { + fireEvent.click(await screen.findByTestId('editTitleBtn')); + }); + + await waitFor(async () => { + expect(await screen.findByTestId('cancelEditBtn')).toBeInTheDocument(); + }); + + act(() => { + fireEvent.click(screen.getByTestId('cancelEditBtn')); + }); + + await waitFor( + async () => { + expect(await screen.findByTestId('editTitleBtn')).toBeInTheDocument(); + }, + { timeout: 5000 }, + ); + }); + + it('add user to group chat', async () => { + setItem('userId', 'hbjguyt7y9890i9otyugttiyuh'); + render( + + + + + , + ); + + await wait(); + await act(async () => { + fireEvent.click(await screen.findByTestId('addMembers')); + }); + + await waitFor(async () => { + expect(await screen.findByTestId('searchUser')).toBeInTheDocument(); + }); + + await act(async () => { + fireEvent.change(await screen.findByTestId('searchUser'), { + target: { value: 'Disha' }, + }); + }); + + await act(async () => { + fireEvent.click(await screen.findByTestId('searchBtn')); + }); + + await wait(); + + await waitFor( + async () => { + expect(await screen.findByTestId('user')).toBeInTheDocument(); + }, + { timeout: 5000 }, + ); + + await act(async () => { + fireEvent.click(await screen.findByTestId('addUserBtn')); + }); + + await wait(10000); + }); + // test case for updating group chat image + it('update group chat image', async () => { + render( + + + + + , + ); + + await wait(); + + await waitFor( + async () => { + expect(await screen.findByTestId('editImageBtn')).toBeInTheDocument(); + }, + { timeout: 5000 }, + ); + + await act(async () => { + fireEvent.click(await screen.findByTestId('editImageBtn')); + }); + + const fileInput = screen.getByTestId('fileInput'); + const smallFile = new File(['small-file-content'], 'small-file.jpg'); // Small file + + Object.defineProperty(fileInput, 'files', { + value: [smallFile], + }); + + fireEvent.change(fileInput); + + await wait(10000); + }); +}); diff --git a/src/components/GroupChatDetails/GroupChatDetails.tsx b/src/components/GroupChatDetails/GroupChatDetails.tsx new file mode 100644 index 0000000000..e252a286be --- /dev/null +++ b/src/components/GroupChatDetails/GroupChatDetails.tsx @@ -0,0 +1,442 @@ +import { Paper, TableBody } from '@mui/material'; +import React, { useRef, useState } from 'react'; +import { Button, Form, ListGroup, Modal } from 'react-bootstrap'; +import styles from './GroupChatDetails.module.css'; +import type { ApolloQueryResult } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; +import { + ADD_USER_TO_GROUP_CHAT, + UPDATE_CHAT, +} from 'GraphQl/Mutations/OrganizationMutations'; +import Table from '@mui/material/Table'; +import TableCell, { tableCellClasses } from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import { styled } from '@mui/material/styles'; +import type { InterfaceQueryUserListItem } from 'utils/interfaces'; +import { USERS_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; +import Loader from 'components/Loader/Loader'; +import { Search, Add } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import Avatar from 'components/Avatar/Avatar'; +import { FiEdit } from 'react-icons/fi'; +import { FaCheck, FaX } from 'react-icons/fa6'; +import convertToBase64 from 'utils/convertToBase64'; +import useLocalStorage from 'utils/useLocalstorage'; + +type DirectMessage = { + _id: string; + createdAt: Date; + sender: { + _id: string; + firstName: string; + lastName: string; + image: string; + }; + replyTo: + | { + _id: string; + createdAt: Date; + sender: { + _id: string; + firstName: string; + lastName: string; + image: string; + }; + messageContent: string; + receiver: { + _id: string; + firstName: string; + lastName: string; + }; + } + | undefined; + messageContent: string; + media: string; +}; + +type Chat = { + _id: string; + isGroup: boolean; + name?: string; + image?: string; + messages: DirectMessage[]; + admins: { + _id: string; + firstName: string; + lastName: string; + email: string; + }[]; + users: { + _id: string; + firstName: string; + lastName: string; + email: string; + }[]; + unseenMessagesByUsers: string; + description: string; +}; + +interface InterfaceGoroupChatDetailsProps { + toggleGroupChatDetailsModal: () => void; + groupChatDetailsModalisOpen: boolean; + chat: Chat; + chatRefetch: ( + variables?: + | Partial<{ + id: string; + }> + | undefined, + ) => Promise>; +} + +const StyledTableCell = styled(TableCell)(({ theme }) => ({ + [`&.${tableCellClasses.head}`]: { + backgroundColor: ['#31bb6b', '!important'], + color: theme.palette.common.white, + }, + [`&.${tableCellClasses.body}`]: { + fontSize: 14, + }, +})); + +const StyledTableRow = styled(TableRow)(() => ({ + '&:last-child td, &:last-child th': { + border: 0, + }, +})); + +/** + * Component for displaying and managing group chat details. + * + * @param props - The component props. + * @param toggleGroupChatDetailsModal - Function to toggle the group chat details modal. + * @param groupChatDetailsModalisOpen - Boolean indicating if the group chat details modal is open. + * @param chat - The chat object containing details about the group chat. + * @param chatRefetch - Function to refetch the chat data. + * @returns The rendered component. + */ +export default function groupChatDetails({ + toggleGroupChatDetailsModal, + groupChatDetailsModalisOpen, + chat, + chatRefetch, +}: InterfaceGoroupChatDetailsProps): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'userChat', + }); + + const [userName, setUserName] = useState(''); + const { getItem } = useLocalStorage(); + + const userId = getItem('userId'); + + const [editChatTitle, setEditChatTitle] = useState(false); + const [chatName, setChatName] = useState(chat?.name || ''); + + const [addUser] = useMutation(ADD_USER_TO_GROUP_CHAT); + + const [addUserModalisOpen, setAddUserModalisOpen] = useState(false); + + function openAddUserModal(): void { + setAddUserModalisOpen(true); + } + + const toggleAddUserModal = (): void => + setAddUserModalisOpen(!addUserModalisOpen); + + const { + data: allUsersData, + loading: allUsersLoading, + refetch: allUsersRefetch, + } = useQuery(USERS_CONNECTION_LIST, { + variables: { + firstName_contains: '', + lastName_contains: '', + }, + }); + + const addUserToGroupChat = async (userId: string): Promise => { + await addUser({ + variables: { + userId, + chatId: chat._id, + }, + }); + }; + + const handleUserModalSearchChange = (e: React.FormEvent): void => { + e.preventDefault(); + /* istanbul ignore next */ + const [firstName, lastName] = userName.split(' '); + + const newFilterData = { + firstName_contains: firstName || '', + lastName_contains: lastName || '', + }; + + allUsersRefetch({ + ...newFilterData, + }); + }; + + const [selectedImage, setSelectedImage] = useState(''); + + const [updateChat] = useMutation(UPDATE_CHAT); + + const fileInputRef = useRef(null); + + const handleImageClick = (): void => { + fileInputRef?.current?.click(); + }; + + const handleImageChange = async ( + e: React.ChangeEvent, + ): Promise => { + const file = e.target.files?.[0]; + if (file) { + const base64 = await convertToBase64(file); + setSelectedImage(base64); + await updateChat(); + await chatRefetch(); + setSelectedImage(''); + } + }; + + return ( + <> + + + {t('groupInfo')} + + + +
+ {chat?.image ? ( + + ) : ( + + )} + + + {editChatTitle ? ( +
+ { + setChatName(e.target.value); + }} + /> + { + await updateChat({ + variables: { + input: { + _id: chat._id, + image: selectedImage ? selectedImage : '', + name: chatName, + }, + }, + }); + setEditChatTitle(false); + await chatRefetch(); + }} + /> + { + setEditChatTitle(false); + setChatName(chat.name || ''); + }} + /> +
+ ) : ( +
+

{chat?.name}

+ { + setEditChatTitle(true); + }} + /> +
+ )} + +

+ {chat?.users.length} {t('members')} +

+

{chat?.description}

+
+ +
+
+ {chat.users.length} {t('members')} +
+ + {chat.admins.map((admin) => admin._id).includes(userId) && ( + { + openAddUserModal(); + }} + > + {t('addMembers')} + + )} + {chat.users.map( + (user: { + _id: string; + firstName: string; + lastName: string; + }) => ( + +
+ + {user.firstName} {user.lastName} +
+ +
+ {chat.admins + .map((admin) => admin._id) + .includes(user._id) && ( +

Admin

+ )} +
+
+ ), + )} +
+
+
+
+ + + {'Chat'} + + + {allUsersLoading ? ( + <> + + + ) : ( + <> +
+
+ { + const { value } = e.target; + setUserName(value); + }} + /> + + +
+ + + + + + # + {'user'} + {'Chat'} + + + + {console.log(allUsersData)} + {allUsersData && + allUsersData.users.length > 0 && + allUsersData.users.map( + ( + userDetails: InterfaceQueryUserListItem, + index: number, + ) => ( + + + {index + 1} + + + {userDetails.user.firstName + + ' ' + + userDetails.user.lastName} +
+ {userDetails.user.email} +
+ + + +
+ ), + )} +
+
+
+ + )} +
+
+ + ); +} diff --git a/src/components/IconComponent/IconComponent.test.tsx b/src/components/IconComponent/IconComponent.spec.tsx similarity index 94% rename from src/components/IconComponent/IconComponent.test.tsx rename to src/components/IconComponent/IconComponent.spec.tsx index 3ba6ccd84d..b398fe7986 100644 --- a/src/components/IconComponent/IconComponent.test.tsx +++ b/src/components/IconComponent/IconComponent.spec.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import IconComponent from './IconComponent'; +import { describe, it, expect } from 'vitest'; const screenTestIdMap: Record> = { MyOrganizations: { @@ -83,6 +84,10 @@ const screenTestIdMap: Record> = { name: 'My Pledges', testId: 'Icon-Component-My-Pledges', }, + LeaveOrganization: { + name: 'Leave Organization', + testId: 'Icon-Component-Leave-Organization', + }, Volunteer: { name: 'Volunteer', testId: 'Icon-Component-Volunteer', diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index 8430aca131..dd104c0408 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -19,6 +19,7 @@ import PostsIcon from 'assets/svgs/posts.svg?react'; import SettingsIcon from 'assets/svgs/settings.svg?react'; import VenueIcon from 'assets/svgs/venues.svg?react'; import RequestsIcon from 'assets/svgs/requests.svg?react'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; import { MdOutlineVolunteerActivism } from 'react-icons/md'; import React from 'react'; @@ -134,6 +135,13 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { stroke={props.fill} /> ); + case 'Leave Organization': + return ( + + ); case 'Volunteer': return ( { test('Component should be rendered properly', () => { diff --git a/src/components/LeftDrawer/LeftDrawer.module.css b/src/components/LeftDrawer/LeftDrawer.module.css deleted file mode 100644 index 86948b9930..0000000000 --- a/src/components/LeftDrawer/LeftDrawer.module.css +++ /dev/null @@ -1,239 +0,0 @@ -.leftDrawer { - width: calc(300px + 2rem); - position: fixed; - top: 0; - bottom: 0; - z-index: 100; - display: flex; - flex-direction: column; - padding: 1rem 1rem 0 1rem; - background-color: #f6f8fc; - transition: 0.5s; - font-family: var(--bs-leftDrawer-font-family); -} - -.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 .talawaLogo { - width: 100%; - height: 65px; -} - -.leftDrawer .talawaText { - font-size: 20px; - text-align: center; - font-weight: 500; -} - -.leftDrawer .titleHeader { - margin: 2rem 0 1rem 0; - font-weight: 600; -} - -.leftDrawer .optionList button { - display: flex; - align-items: center; - width: 100%; - text-align: start; - margin-bottom: 0.8rem; - border-radius: 16px; - outline: none; - border: none; -} - -.leftDrawer .optionList button .iconWrapper { - width: 36px; -} - -.leftDrawer .profileContainer { - border: none; - width: 100%; - padding: 2.1rem 0.5rem; - height: 52px; - display: flex; - align-items: center; - background-color: var(--bs-white); -} - -.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; - } - - .inactiveDrawer { - opacity: 0; - left: 0; - z-index: -1; - animation: closeDrawer 0.4s ease-in-out; - } - - .activeDrawer { - display: flex; - z-index: 100; - animation: openDrawer 0.6s ease-in-out; - } - - .logout { - margin-bottom: 2.5rem !important; - } -} - -@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/LeftDrawer/LeftDrawer.tsx b/src/components/LeftDrawer/LeftDrawer.tsx index c8c7d8486e..eabf9722f8 100644 --- a/src/components/LeftDrawer/LeftDrawer.tsx +++ b/src/components/LeftDrawer/LeftDrawer.tsx @@ -6,7 +6,7 @@ import OrganizationsIcon from 'assets/svgs/organizations.svg?react'; import RolesIcon from 'assets/svgs/roles.svg?react'; import SettingsIcon from 'assets/svgs/settings.svg?react'; import TalawaLogo from 'assets/svgs/talawa.svg?react'; -import styles from './LeftDrawer.module.css'; +import styles from 'style/app.module.css'; import useLocalStorage from 'utils/useLocalstorage'; export interface InterfaceLeftDrawerProps { diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.spec.tsx similarity index 98% rename from src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx rename to src/components/LeftDrawerOrg/LeftDrawerOrg.spec.tsx index 71f3593499..3fa6c0205e 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.spec.tsx @@ -1,7 +1,7 @@ +import '@testing-library/jest-dom'; import React, { act } from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter, MemoryRouter } from 'react-router-dom'; @@ -15,6 +15,7 @@ import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi, describe, test, expect } from 'vitest'; const { setItem } = useLocalStorage(); @@ -65,7 +66,7 @@ const props: InterfaceLeftDrawerProps = { }, ], hideDrawer: false, - setHideDrawer: jest.fn(), + setHideDrawer: vi.fn(), }; const MOCKS = [ @@ -244,11 +245,11 @@ const defaultScreens = [ 'All Organizations', ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); @@ -275,7 +276,7 @@ beforeEach(() => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); localStorage.clear(); }); diff --git a/src/components/Loader/Loader.test.tsx b/src/components/Loader/Loader.spec.tsx similarity index 76% rename from src/components/Loader/Loader.test.tsx rename to src/components/Loader/Loader.spec.tsx index c512b480e3..ab312a9155 100644 --- a/src/components/Loader/Loader.test.tsx +++ b/src/components/Loader/Loader.spec.tsx @@ -1,23 +1,24 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import Loader from './Loader'; +import { describe, it, expect } from 'vitest'; describe('Testing Loader component', () => { - test('Component should be rendered properly', () => { + it('Component should be rendered properly', () => { render(); expect(screen.getByTestId('spinner-wrapper')).toBeInTheDocument(); expect(screen.getByTestId('spinner')).toBeInTheDocument(); }); - test('Component should render on custom sizes', () => { + it('Component should render on custom sizes', () => { render(); expect(screen.getByTestId('spinner-wrapper')).toBeInTheDocument(); expect(screen.getByTestId('spinner')).toBeInTheDocument(); }); - test('Component should render with large size', () => { + it('Component should render with large size', () => { render(); expect(screen.getByTestId('spinner-wrapper')).toBeInTheDocument(); diff --git a/src/components/LoginPortalToggle/LoginPortalToggle.module.css b/src/components/LoginPortalToggle/LoginPortalToggle.module.css index 227f65aa9a..db51554389 100644 --- a/src/components/LoginPortalToggle/LoginPortalToggle.module.css +++ b/src/components/LoginPortalToggle/LoginPortalToggle.module.css @@ -22,13 +22,13 @@ } .activeLink { - color: var(--bs-white); + color: white; border: 1px solid var(--bs-primary); - background-color: var(--active-button-bg); + background-color: var(--toggle-button-bg); } .activeLink:hover { color: var(--bs-white); - background-color: var(--active-hover); + background-color: var(--toggle-button-bg); border: 1px solid var(--bs-primary); } diff --git a/src/components/LoginPortalToggle/LoginPortalToggle.test.tsx b/src/components/LoginPortalToggle/LoginPortalToggle.spec.tsx similarity index 92% rename from src/components/LoginPortalToggle/LoginPortalToggle.test.tsx rename to src/components/LoginPortalToggle/LoginPortalToggle.spec.tsx index 327e6cccea..eee24f6376 100644 --- a/src/components/LoginPortalToggle/LoginPortalToggle.test.tsx +++ b/src/components/LoginPortalToggle/LoginPortalToggle.spec.tsx @@ -6,6 +6,7 @@ import { I18nextProvider } from 'react-i18next'; import LoginPortalToggle from './LoginPortalToggle'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; +import { describe, test, vi } from 'vitest'; async function wait(ms = 100): Promise { await act(() => { @@ -17,7 +18,7 @@ async function wait(ms = 100): Promise { describe('Testing LoginPortalToggle component', () => { test('Component Should be rendered properly', async () => { - const mockOnToggle = jest.fn(); + const mockOnToggle = vi.fn(); render( diff --git a/src/components/MemberDetail/EventsAttendedByMember.test.tsx b/src/components/MemberDetail/EventsAttendedByMember.spec.tsx similarity index 100% rename from src/components/MemberDetail/EventsAttendedByMember.test.tsx rename to src/components/MemberDetail/EventsAttendedByMember.spec.tsx diff --git a/src/components/MemberDetail/EventsAttendedCardItem.test.tsx b/src/components/MemberDetail/EventsAttendedCardItem.spec.tsx similarity index 96% rename from src/components/MemberDetail/EventsAttendedCardItem.test.tsx rename to src/components/MemberDetail/EventsAttendedCardItem.spec.tsx index afbb19eeea..8694426d58 100644 --- a/src/components/MemberDetail/EventsAttendedCardItem.test.tsx +++ b/src/components/MemberDetail/EventsAttendedCardItem.spec.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import EventAttendedCard from './EventsAttendedCardItem'; +import { vi } from 'vitest'; interface InterfaceEventAttendedCardProps { type: 'Event'; @@ -33,7 +34,7 @@ describe('EventAttendedCard', () => { }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('renders event details correctly', () => { diff --git a/src/components/MemberDetail/EventsAttendedMemberModal.test.tsx b/src/components/MemberDetail/EventsAttendedMemberModal.spec.tsx similarity index 91% rename from src/components/MemberDetail/EventsAttendedMemberModal.test.tsx rename to src/components/MemberDetail/EventsAttendedMemberModal.spec.tsx index ebdc3fff4c..fdec64e5f0 100644 --- a/src/components/MemberDetail/EventsAttendedMemberModal.test.tsx +++ b/src/components/MemberDetail/EventsAttendedMemberModal.spec.tsx @@ -3,15 +3,24 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { MockedProvider } from '@apollo/client/testing'; import { BrowserRouter } from 'react-router-dom'; import EventsAttendedMemberModal from './EventsAttendedMemberModal'; +import { vi } from 'vitest'; -jest.mock('react-i18next', () => ({ +/** + * Mock the `react-i18next` module to provide translation functionality. + */ + +vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key, i18n: { changeLanguage: () => Promise.resolve() }, }), })); -jest.mock('./customTableCell', () => ({ +/** + * Mock the `CustomTableCell` component for testing. + */ + +vi.mock('./customTableCell', () => ({ CustomTableCell: ({ eventId }: { eventId: string }) => ( {`Event ${eventId}`} @@ -33,12 +42,12 @@ const mockEvents = Array.from({ length: 6 }, (_, index) => ({ describe('EventsAttendedMemberModal', () => { const defaultProps = { eventsAttended: mockEvents, - setShow: jest.fn(), + setShow: vi.fn(), show: true, }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); test('renders modal with correct title when show is true', () => { @@ -95,7 +104,7 @@ describe('EventsAttendedMemberModal', () => { }); test('closes modal when close button is clicked', () => { - const mockSetShow = jest.fn(); + const mockSetShow = vi.fn(); render( diff --git a/src/components/MemberDetail/customTableCell.spec.tsx b/src/components/MemberDetail/customTableCell.spec.tsx new file mode 100644 index 0000000000..81a088d5bb --- /dev/null +++ b/src/components/MemberDetail/customTableCell.spec.tsx @@ -0,0 +1,160 @@ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import { MockedProvider } from '@apollo/client/testing'; +import { BrowserRouter } from 'react-router-dom'; +import { CustomTableCell } from './customTableCell'; +import { EVENT_DETAILS } from 'GraphQl/Queries/Queries'; +import { vi } from 'vitest'; +vi.mock('react-toastify', () => ({ + toast: { + success: vi.fn(), + error: vi.fn(), + }, +})); + +const mocks = [ + { + request: { + query: EVENT_DETAILS, + variables: { id: 'event123' }, + }, + result: { + data: { + event: { + _id: 'event123', + title: 'Test Event', + description: 'This is a test event description', + startDate: '2023-05-01', + endDate: '2023-05-02', + startTime: '09:00:00', + endTime: '17:00:00', + allDay: false, + location: 'Test Location', + recurring: true, + baseRecurringEvent: { + _id: 'recurringEvent123', + }, + organization: { + _id: 'org456', + members: [ + { _id: 'member1', firstName: 'John', lastName: 'Doe' }, + { _id: 'member2', firstName: 'Jane', lastName: 'Smith' }, + ], + }, + attendees: [{ _id: 'user1' }, { _id: 'user2' }], + }, + }, + }, + }, +]; + +describe('CustomTableCell', () => { + it('renders event details correctly', async () => { + render( + + + + + + +
+
+
, + ); + + await waitFor(() => screen.getByTestId('custom-row')); + + expect(screen.getByText('Test Event')).toBeInTheDocument(); + expect( + screen.getByText( + new Date('2023-05-01').toLocaleDateString(undefined, { + year: 'numeric', + month: 'long', + day: 'numeric', + timeZone: 'UTC', + }), + ), + ).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + expect(screen.getByText('2')).toBeInTheDocument(); + + const link = screen.getByRole('link', { name: 'Test Event' }); + expect(link).toHaveAttribute('href', '/event/org456/event123'); + }); + + it('displays loading state', () => { + render( + + + + + +
+
, + ); + + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + }); + + it('displays error state', async () => { + const errorMock = [ + { + request: { + query: EVENT_DETAILS, + variables: { id: 'event123' }, + }, + error: new Error('An error occurred'), + }, + ]; + + render( + + + + + +
+
, + ); + + await waitFor(() => { + expect( + screen.getByText( + 'Unable to load event details. Please try again later.', + ), + ).toBeInTheDocument(); + }); + }); + + it('displays no event found message', async () => { + const noEventMock = [ + { + request: { + query: EVENT_DETAILS, + variables: { id: 'event123' }, + }, + result: { + data: { + event: null, + }, + }, + }, + ]; + + render( + + + + + +
+
, + ); + + await waitFor(() => { + expect( + screen.getByText('Event not found or has been deleted'), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/MemberDetail/customTableCell.test.tsx b/src/components/MemberDetail/customTableCell.test.tsx deleted file mode 100644 index bc296a74f3..0000000000 --- a/src/components/MemberDetail/customTableCell.test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import { MockedProvider } from '@apollo/client/testing'; -import { BrowserRouter } from 'react-router-dom'; -import { CustomTableCell } from './customTableCell'; -import { EVENT_DETAILS } from 'GraphQl/Queries/Queries'; - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - error: jest.fn(), - }, -})); - -const mocks = [ - { - request: { - query: EVENT_DETAILS, - variables: { id: 'event123' }, - }, - result: { - data: { - event: { - _id: 'event123', - title: 'Test Event', - description: 'This is a test event description', - startDate: '2023-05-01', - endDate: '2023-05-02', - startTime: '09:00:00', - endTime: '17:00:00', - allDay: false, - location: 'Test Location', - recurring: true, - baseRecurringEvent: { - _id: 'recurringEvent123', - }, - organization: { - _id: 'org456', - members: [ - { _id: 'member1', firstName: 'John', lastName: 'Doe' }, - { _id: 'member2', firstName: 'Jane', lastName: 'Smith' }, - ], - }, - attendees: [{ _id: 'user1' }, { _id: 'user2' }], - }, - }, - }, - }, -]; - -describe('CustomTableCell', () => { - it('renders event details correctly', async () => { - render( - - - - - - -
-
-
, - ); - - await waitFor(() => screen.getByTestId('custom-row')); - - expect(screen.getByText('Test Event')).toBeInTheDocument(); - expect(screen.getByText('May 1, 2023')).toBeInTheDocument(); - expect(screen.getByText('Yes')).toBeInTheDocument(); - expect(screen.getByText('2')).toBeInTheDocument(); - - const link = screen.getByRole('link', { name: 'Test Event' }); - expect(link).toHaveAttribute('href', '/event/org456/event123'); - }); - - it('displays loading state', () => { - render( - - - - - -
-
, - ); - - expect(screen.getByRole('progressbar')).toBeInTheDocument(); - }); - - // it('displays error state', async () => { - // const errorMock = [ - // { - // request: { - // query: EVENT_DETAILS, - // variables: { id: 'event123' }, - // }, - // error: new Error('An error occurred'), - // }, - // ]; - - // render( - // - // - // - // - // - //
- //
, - // ); - - // await waitFor( - // () => { - // expect( - // screen.getByText('Error loading event details'), - // ).toBeInTheDocument(); - // }, - // { timeout: 2000 }, - // ); - - // // Check if the error message from toast has been called - // expect(toast.error).toHaveBeenCalledWith('An error occurred'); - // }); - - // it('displays no event found message', async () => { - // const noEventMock = [ - // { - // request: { - // query: EVENT_DETAILS, - // variables: { id: 'event123' }, - // }, - // result: { - // data: { - // event: { - // _id: null, - // title: null, - // startDate: null, - // description: null, - // endDate: null, - // startTime: null, - // endTime: null, - // allDay: false, - // location: null, - // recurring: null, - // organization: { - // _id: null, - // members: [], - // }, - // baseRecurringEvent: { - // _id: 'recurringEvent123', - // }, - // attendees: [], - // }, - // }, - // }, - // }, - // ]; - - // render( - // - // - // - // - // - //
- //
, - // ); - - // await waitFor(() => screen.getByText('No event found')); - // expect(screen.getByText('No event found')).toBeInTheDocument(); - // }); -}); diff --git a/src/components/MemberRequestCard/MemberRequestCard.test.tsx b/src/components/MemberRequestCard/MemberRequestCard.spec.tsx similarity index 97% rename from src/components/MemberRequestCard/MemberRequestCard.test.tsx rename to src/components/MemberRequestCard/MemberRequestCard.spec.tsx index a38a046ea2..5779d3688a 100644 --- a/src/components/MemberRequestCard/MemberRequestCard.test.tsx +++ b/src/components/MemberRequestCard/MemberRequestCard.spec.tsx @@ -11,6 +11,7 @@ import { import MemberRequestCard from './MemberRequestCard'; import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; +import { describe, vi, expect } from 'vitest'; const MOCKS = [ { @@ -65,7 +66,7 @@ describe('Testing Member Request Card', () => { email: 'johndoe@gmail.com', }; - global.alert = jest.fn(); + global.alert = vi.fn(); it('should render props and text elements test for the page component', async () => { global.confirm = (): boolean => true; diff --git a/src/components/NotFound/NotFound.test.tsx b/src/components/NotFound/NotFound.spec.tsx similarity index 94% rename from src/components/NotFound/NotFound.test.tsx rename to src/components/NotFound/NotFound.spec.tsx index 54c0bcfe4a..a70e355f7a 100644 --- a/src/components/NotFound/NotFound.test.tsx +++ b/src/components/NotFound/NotFound.spec.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; - import { render, screen } from '@testing-library/react'; import NotFound from './NotFound'; +import { expect, it, describe } from 'vitest'; describe('Tesing the NotFound Component', () => { it('renders the component with the correct title for posts', () => { diff --git a/src/components/OrgAdminListCard/OrgAdminListCard.test.tsx b/src/components/OrgAdminListCard/OrgAdminListCard.spec.tsx similarity index 82% rename from src/components/OrgAdminListCard/OrgAdminListCard.test.tsx rename to src/components/OrgAdminListCard/OrgAdminListCard.spec.tsx index 7baea946d2..a68b253c0a 100644 --- a/src/components/OrgAdminListCard/OrgAdminListCard.test.tsx +++ b/src/components/OrgAdminListCard/OrgAdminListCard.spec.tsx @@ -9,6 +9,7 @@ import OrgAdminListCard from './OrgAdminListCard'; import i18nForTest from 'utils/i18nForTest'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { StaticMockLink } from 'utils/StaticMockLink'; +import { vi, beforeEach, afterEach, expect, it, describe } from 'vitest'; const MOCKS = [ { @@ -57,27 +58,28 @@ const renderOrgAdminListCard = (props: {
, ); }; -jest.mock('i18next-browser-languagedetector', () => ({ - init: jest.fn(), +vi.mock('i18next-browser-languagedetector', async () => ({ + ...(await vi.importActual('i18next-browser-languagedetector')), + init: vi.fn(), type: 'languageDetector', - detect: jest.fn(() => 'en'), - cacheUserLanguage: jest.fn(), + detect: vi.fn(() => 'en'), + cacheUserLanguage: vi.fn(), })); describe('Testing Organization Admin List Card', () => { - global.alert = jest.fn(); + global.alert = vi.fn(); beforeEach(() => { Object.defineProperty(window, 'location', { writable: true, - value: { reload: jest.fn() }, + value: { reload: vi.fn() }, }); }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); - test('should render props and text elements test for the page component', async () => { + it('should render props and text elements test for the page component', async () => { const props = { toggleRemoveModal: () => true, id: '456', @@ -92,7 +94,7 @@ describe('Testing Organization Admin List Card', () => { await wait(2000); }); - test('Should not render text elements when props value is not passed', async () => { + it('Should not render text elements when props value is not passed', async () => { const props = { toggleRemoveModal: () => true, id: undefined, diff --git a/src/components/OrgContriCards/OrgContriCards.module.css b/src/components/OrgContriCards/OrgContriCards.module.css deleted file mode 100644 index d20b696621..0000000000 --- a/src/components/OrgContriCards/OrgContriCards.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.cards { - width: 45%; - background: #fcfcfc; - margin: 10px 20px; - padding: 20px 30px; - border-radius: 5px; - border: 1px solid #e8e8e8; - box-shadow: 0 3px 5px #c9c9c9; - margin-right: 40px; - color: #737373; -} -.cards > h2 { - font-size: 19px; -} -.cards > h3 { - font-size: 17px; -} -.cards > p { - font-size: 14px; - margin-top: -5px; - margin-bottom: 7px; -} diff --git a/src/components/OrgContriCards/OrgContriCards.test.tsx b/src/components/OrgContriCards/OrgContriCards.spec.tsx similarity index 97% rename from src/components/OrgContriCards/OrgContriCards.test.tsx rename to src/components/OrgContriCards/OrgContriCards.spec.tsx index 4f202cd355..57a85dc451 100644 --- a/src/components/OrgContriCards/OrgContriCards.test.tsx +++ b/src/components/OrgContriCards/OrgContriCards.spec.tsx @@ -7,7 +7,7 @@ import { I18nextProvider } from 'react-i18next'; import OrgContriCards from './OrgContriCards'; import i18nForTest from 'utils/i18nForTest'; import { BACKEND_URL } from 'Constant/constant'; - +import { describe, expect } from 'vitest'; const client: ApolloClient = new ApolloClient({ cache: new InMemoryCache(), uri: BACKEND_URL, diff --git a/src/components/OrgContriCards/OrgContriCards.tsx b/src/components/OrgContriCards/OrgContriCards.tsx index 6635be09b8..84237013c8 100644 --- a/src/components/OrgContriCards/OrgContriCards.tsx +++ b/src/components/OrgContriCards/OrgContriCards.tsx @@ -3,7 +3,7 @@ import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import { useTranslation } from 'react-i18next'; -import styles from './OrgContriCards.module.css'; +import styles from '../../style/app.module.css'; /** * Props for the OrgContriCards component diff --git a/src/components/OrgDelete/OrgDelete.test.tsx b/src/components/OrgDelete/OrgDelete.spec.tsx similarity index 86% rename from src/components/OrgDelete/OrgDelete.test.tsx rename to src/components/OrgDelete/OrgDelete.spec.tsx index b9b9ca2572..23f8dcdde5 100644 --- a/src/components/OrgDelete/OrgDelete.test.tsx +++ b/src/components/OrgDelete/OrgDelete.spec.tsx @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react'; import type { NormalizedCacheObject } from '@apollo/client'; import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'; import { I18nextProvider } from 'react-i18next'; - +import { describe, it, expect } from 'vitest'; import OrgDelete from './OrgDelete'; import i18nForTest from 'utils/i18nForTest'; import { BACKEND_URL } from 'Constant/constant'; @@ -14,7 +14,7 @@ const client: ApolloClient = new ApolloClient({ }); describe('Testing Organization People List Card', () => { - test('should render props and text elements test for the page component', () => { + it('should render props and text elements test for the page component', () => { render( diff --git a/src/components/OrgListCard/OrgListCard.test.tsx b/src/components/OrgListCard/OrgListCard.spec.tsx similarity index 73% rename from src/components/OrgListCard/OrgListCard.test.tsx rename to src/components/OrgListCard/OrgListCard.spec.tsx index 4072265ea4..c6e6de8b4a 100644 --- a/src/components/OrgListCard/OrgListCard.test.tsx +++ b/src/components/OrgListCard/OrgListCard.spec.tsx @@ -1,20 +1,28 @@ -import React, { act } from 'react'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; import { render, screen } from '@testing-library/react'; -import 'jest-location-mock'; +import userEvent from '@testing-library/user-event'; +import { describe, test, expect, vi, beforeEach } from 'vitest'; import { I18nextProvider } from 'react-i18next'; +import { BrowserRouter } from 'react-router-dom'; +import { MockedProvider } from '@apollo/client/testing'; import i18nForTest from 'utils/i18nForTest'; import type { InterfaceOrgListCardProps } from './OrgListCard'; import OrgListCard from './OrgListCard'; -import userEvent from '@testing-library/user-event'; -import { BrowserRouter } from 'react-router-dom'; import { IS_SAMPLE_ORGANIZATION_QUERY } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; -import { MockedProvider } from '@apollo/react-testing'; import useLocalStorage from 'utils/useLocalstorage'; const { setItem, removeItem } = useLocalStorage(); +// Mock window.location +const mockAssign = vi.fn(); +Object.defineProperty(window, 'location', { + value: { assign: mockAssign }, + writable: true, +}); + const MOCKS = [ { request: { @@ -75,6 +83,10 @@ const props: InterfaceOrgListCardProps = { }; describe('Testing the Super Dash List', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + test('should render props and text elements test for the page component', async () => { removeItem('id'); setItem('id', '123'); // Means the user is an admin @@ -88,22 +100,24 @@ describe('Testing the Super Dash List', () => {
, ); + await wait(); - expect(screen.getByAltText(/Dogs Care image/i)).toBeInTheDocument(); - expect(screen.getByText(/Admins:/i)).toBeInTheDocument(); - expect(screen.getByText(/Members:/i)).toBeInTheDocument(); - expect(screen.getByText('Dogs Care')).toBeInTheDocument(); - expect(screen.getByText(/Sample City/i)).toBeInTheDocument(); - expect(screen.getByText(/123 Sample Street/i)).toBeInTheDocument(); - expect(screen.getByTestId(/manageBtn/i)).toBeInTheDocument(); - expect(screen.getByTestId(/flaskIcon/i)).toBeInTheDocument(); - userEvent.click(screen.getByTestId(/manageBtn/i)); + + expect(screen.getByAltText(/Dogs Care image/i)).toBeDefined(); + expect(screen.getByText(/Admins:/i)).toBeDefined(); + expect(screen.getByText(/Members:/i)).toBeDefined(); + expect(screen.getByText('Dogs Care')).toBeDefined(); + expect(screen.getByText(/Sample City/i)).toBeDefined(); + expect(screen.getByText(/123 Sample Street/i)).toBeDefined(); + expect(screen.getByTestId(/manageBtn/i)).toBeDefined(); + expect(screen.getByTestId(/flaskIcon/i)).toBeDefined(); + + await userEvent.click(screen.getByTestId(/manageBtn/i)); removeItem('id'); }); test('Testing if the props data is not provided', () => { window.location.assign('/orgdash'); - render( @@ -114,7 +128,7 @@ describe('Testing the Super Dash List', () => { , ); - expect(window.location).toBeAt('/orgdash'); + expect(mockAssign).toHaveBeenCalledWith('/orgdash'); }); test('Testing if component is rendered properly when image is null', () => { @@ -122,6 +136,7 @@ describe('Testing the Super Dash List', () => { ...props, ...{ data: { ...props.data, ...{ image: null } } }, }; + render( @@ -131,10 +146,11 @@ describe('Testing the Super Dash List', () => { , ); - expect(screen.getByTestId(/emptyContainerForImage/i)).toBeInTheDocument(); + + expect(screen.getByTestId(/emptyContainerForImage/i)).toBeDefined(); }); - test('Testing if user is redirected to orgDash screen', () => { + test('Testing if user is redirected to orgDash screen', async () => { render( @@ -144,6 +160,7 @@ describe('Testing the Super Dash List', () => { , ); - userEvent.click(screen.getByTestId('manageBtn')); + + await userEvent.click(screen.getByTestId('manageBtn')); }); }); diff --git a/src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx b/src/components/OrgPeopleListCard/OrgPeopleListCard.spec.tsx similarity index 96% rename from src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx rename to src/components/OrgPeopleListCard/OrgPeopleListCard.spec.tsx index 7cee31107f..0fb2c39599 100644 --- a/src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx +++ b/src/components/OrgPeopleListCard/OrgPeopleListCard.spec.tsx @@ -9,7 +9,7 @@ import { REMOVE_MEMBER_MUTATION } from 'GraphQl/Mutations/mutations'; import i18nForTest from 'utils/i18nForTest'; import { BrowserRouter } from 'react-router-dom'; import { StaticMockLink } from 'utils/StaticMockLink'; - +import { describe, test, expect, vi } from 'vitest'; const MOCKS = [ { request: { @@ -41,7 +41,7 @@ describe('Testing Organization People List Card', () => { toggleRemoveModal: () => true, id: '1', }; - global.alert = jest.fn(); + global.alert = vi.fn(); test('should render props and text elements test for the page component', async () => { global.confirm = (): boolean => true; diff --git a/src/components/OrgPostCard/OrgPostCard.test.tsx b/src/components/OrgPostCard/OrgPostCard.spec.tsx similarity index 86% rename from src/components/OrgPostCard/OrgPostCard.test.tsx rename to src/components/OrgPostCard/OrgPostCard.spec.tsx index 7105e5e8f2..5364766aee 100644 --- a/src/components/OrgPostCard/OrgPostCard.test.tsx +++ b/src/components/OrgPostCard/OrgPostCard.spec.tsx @@ -10,7 +10,6 @@ import { MockedProvider } from '@apollo/react-testing'; import OrgPostCard from './OrgPostCard'; import { I18nextProvider } from 'react-i18next'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { DELETE_POST_MUTATION, UPDATE_POST_MUTATION, @@ -21,6 +20,36 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import convertToBase64 from 'utils/convertToBase64'; import { BrowserRouter } from 'react-router-dom'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; + +/** + * Unit tests for the OrgPostCard component, which displays organization posts with various interactions. + * + * These tests verify: + * - Basic rendering and display functionality: + * - Proper rendering of post content (title, text, images, videos) + * - "Read more" toggle button behavior + * - Image and video display handling + * - Fallback behavior when media is missing + * + * - Modal interactions: + * - Opening/closing primary modal on post click + * - Secondary modal functionality for edit/delete operations + * - Form validation in edit modal + * - Media upload handling in edit modal + * + * - Post management operations: + * - Creating and updating posts + * - Deleting posts + * - Pinning/unpinning posts + * - Error handling for failed operations + * + * - Media handling: + * - Image upload and preview + * - Video upload and preview + * - Auto-play behavior on hover + * - Clearing uploaded media + */ const { setItem } = useLocalStorage(); @@ -71,19 +100,23 @@ const MOCKS = [ }, }, ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); -jest.mock('i18next-browser-languagedetector', () => ({ - init: jest.fn(), - type: 'languageDetector', - detect: jest.fn(() => 'en'), - cacheUserLanguage: jest.fn(), -})); +vi.mock('i18next-browser-languagedetector', () => { + return { + default: { + init: vi.fn(), + type: 'languageDetector', + detect: vi.fn(() => 'en'), + cacheUserLanguage: vi.fn(), + }, + }; +}); const link = new StaticMockLink(MOCKS, true); async function wait(ms = 100): Promise { await act(() => { @@ -99,7 +132,7 @@ describe('Testing Organization Post Card', () => { Object.defineProperty(window, 'location', { configurable: true, value: { - reload: jest.fn(), + reload: vi.fn(), }, }); }); @@ -122,20 +155,16 @@ describe('Testing Organization Post Card', () => { pinned: false, }; - jest.mock('react-toastify', () => ({ + vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); - jest.mock('react', () => ({ - ...jest.requireActual('react'), - useRef: jest.fn(), - })); - global.alert = jest.fn(); + global.alert = vi.fn(); - test('Opens post on image click', () => { + it('Opens post on image click', () => { const { getByTestId, getByAltText } = render( @@ -149,7 +178,7 @@ describe('Testing Organization Post Card', () => { expect(getByTestId('card-title')).toBeInTheDocument(); expect(getByAltText('image')).toBeInTheDocument(); }); - test('renders with default props', () => { + it('renders with default props', () => { const { getByAltText, getByTestId } = render( @@ -161,7 +190,7 @@ describe('Testing Organization Post Card', () => { expect(getByTestId('card-title')).toBeInTheDocument(); expect(getByAltText('image')).toBeInTheDocument(); }); - test('toggles "Read more" button', () => { + it('toggles "Read more" button', () => { const { getByTestId } = render( @@ -176,7 +205,7 @@ describe('Testing Organization Post Card', () => { fireEvent.click(toggleButton); expect(toggleButton).toHaveTextContent('Read more'); }); - test('opens and closes edit modal', async () => { + it('opens and closes edit modal', async () => { setItem('id', '123'); render( @@ -196,7 +225,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(createOrgBtn); userEvent.click(screen.getByTestId('closeOrganizationModal')); }); - test('Should render text elements when props value is not passed', async () => { + it('Should render text elements when props value is not passed', async () => { global.confirm = (): boolean => false; render( @@ -209,7 +238,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByAltText('image')); expect(screen.getByAltText('Post Image')).toBeInTheDocument(); }); - test('Testing post updating after post is updated', async () => { + it('Testing post updating after post is updated', async () => { const { getByTestId } = render( @@ -265,7 +294,7 @@ describe('Testing Organization Post Card', () => { await waitFor(() => { convertToBase64(file); // Replace with the expected base64-encoded image }); - document.getElementById = jest.fn(() => input); + document.getElementById = vi.fn(() => input); const clearImageButton = getByTestId('closeimage'); fireEvent.click(clearImageButton); } @@ -278,7 +307,7 @@ describe('Testing Organization Post Card', () => { { timeout: 2500 }, ); }); - test('Testing post updating functionality fail case', async () => { + it('Testing post updating functionality fail case', async () => { const props2 = { id: '', postID: '123', @@ -343,7 +372,7 @@ describe('Testing Organization Post Card', () => { await waitFor(() => { convertToBase64(file); // Replace with the expected base64-encoded image }); - document.getElementById = jest.fn(() => input); + document.getElementById = vi.fn(() => input); const clearImageButton = getByTestId('closeimage'); fireEvent.click(clearImageButton); } @@ -356,7 +385,7 @@ describe('Testing Organization Post Card', () => { { timeout: 2500 }, ); }); - test('Testing pin post functionality', async () => { + it('Testing pin post functionality', async () => { render( @@ -378,7 +407,7 @@ describe('Testing Organization Post Card', () => { { timeout: 3000 }, ); }); - test('Testing pin post functionality fail case', async () => { + it('Testing pin post functionality fail case', async () => { const props2 = { id: '', postID: '123', @@ -403,7 +432,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByTestId('moreiconbtn')); userEvent.click(screen.getByTestId('pinpostBtn')); }); - test('Testing post delete functionality', async () => { + it('Testing post delete functionality', async () => { render( @@ -429,7 +458,7 @@ describe('Testing Organization Post Card', () => { { timeout: 3000 }, ); }); - test('Testing post delete functionality fail case', async () => { + it('Testing post delete functionality fail case', async () => { const props2 = { id: '', postID: '123', @@ -458,7 +487,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByTestId('deletePostModalBtn')); fireEvent.click(screen.getByTestId('deletePostBtn')); }); - test('Testing close functionality of primary modal', async () => { + it('Testing close functionality of primary modal', async () => { render( @@ -475,7 +504,7 @@ describe('Testing Organization Post Card', () => { //Primary Modal is closed expect(screen.queryByTestId('moreiconbtn')).not.toBeInTheDocument(); }); - test('Testing close functionality of secondary modal', async () => { + it('Testing close functionality of secondary modal', async () => { render( @@ -496,7 +525,7 @@ describe('Testing Organization Post Card', () => { expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); }); - test('renders without "Read more" button when postInfo length is less than or equal to 43', () => { + it('renders without "Read more" button when postInfo length is less than or equal to 43', () => { render( @@ -506,7 +535,7 @@ describe('Testing Organization Post Card', () => { ); expect(screen.queryByTestId('toggleBtn')).not.toBeInTheDocument(); }); - test('renders with "Read more" button when postInfo length is more than 43', () => { + it('renders with "Read more" button when postInfo length is more than 43', () => { const props2 = { id: '12', postID: '123', @@ -529,7 +558,7 @@ describe('Testing Organization Post Card', () => { expect(screen.getByTestId('toggleBtn')).toBeInTheDocument(); }); - test('updates state variables correctly when handleEditModal is called', () => { + it('updates state variables correctly when handleEditModal is called', () => { const link2 = new StaticMockLink(MOCKS, true); render( @@ -555,7 +584,7 @@ describe('Testing Organization Post Card', () => { expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); }); - test('updates state variables correctly when handleDeleteModal is called', () => { + it('updates state variables correctly when handleDeleteModal is called', () => { const link2 = new StaticMockLink(MOCKS, true); render( @@ -581,7 +610,7 @@ describe('Testing Organization Post Card', () => { expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); }); - test('clears postvideo state and resets file input value', async () => { + it('clears postvideo state and resets file input value', async () => { const { getByTestId } = render( @@ -615,7 +644,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByTestId('closePreview')); } }); - test('clears postimage state and resets file input value', async () => { + it('clears postimage state and resets file input value', async () => { const { getByTestId } = render( @@ -647,12 +676,12 @@ describe('Testing Organization Post Card', () => { await waitFor(() => { convertToBase64(file); // Replace with the expected base64-encoded image }); - document.getElementById = jest.fn(() => input); + document.getElementById = vi.fn(() => input); const clearImageButton = getByTestId('closeimage'); fireEvent.click(clearImageButton); } }); - test('clears postitle state and resets file input value', async () => { + it('clears postitle state and resets file input value', async () => { const { getByTestId } = render( @@ -677,7 +706,7 @@ describe('Testing Organization Post Card', () => { expect(screen.getByTestId('closeOrganizationModal')).toBeInTheDocument(); expect(screen.getByTestId('updatePostBtn')).toBeInTheDocument(); }); - test('clears postinfo state and resets file input value', async () => { + it('clears postinfo state and resets file input value', async () => { const { getByTestId } = render( @@ -702,7 +731,7 @@ describe('Testing Organization Post Card', () => { expect(screen.getByTestId('closeOrganizationModal')).toBeInTheDocument(); expect(screen.getByTestId('updatePostBtn')).toBeInTheDocument(); }); - test('Testing create organization modal', async () => { + it('Testing create organization modal', async () => { setItem('id', '123'); render( @@ -724,7 +753,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(createOrgBtn); userEvent.click(screen.getByTestId('closeOrganizationModal')); }); - test('should toggle post pin when pin button is clicked', async () => { + it('should toggle post pin when pin button is clicked', async () => { const { getByTestId } = render( @@ -744,6 +773,12 @@ describe('Testing Organization Post Card', () => { }); }); test('testing video play and pause on mouse enter and leave events', async () => { + const playMock = vi.fn(); + const pauseMock = vi.fn(); + + HTMLMediaElement.prototype.play = playMock; + HTMLMediaElement.prototype.pause = pauseMock; + const { getByTestId } = render( @@ -754,16 +789,14 @@ describe('Testing Organization Post Card', () => { const card = getByTestId('cardVid'); - HTMLVideoElement.prototype.play = jest.fn(); - HTMLVideoElement.prototype.pause = jest.fn(); - fireEvent.mouseEnter(card); - expect(HTMLVideoElement.prototype.play).toHaveBeenCalled(); + expect(playMock).toHaveBeenCalledTimes(1); fireEvent.mouseLeave(card); - expect(HTMLVideoElement.prototype.pause).toHaveBeenCalled(); + expect(pauseMock).toHaveBeenCalledTimes(1); }); - test('for rendering when no image and no video is available', async () => { + + it('for rendering when no image and no video is available', async () => { const props2 = { id: '', postID: '123', diff --git a/src/components/OrgSettings/ActionItemCategories/CategoryModal.test.tsx b/src/components/OrgSettings/ActionItemCategories/CategoryModal.spec.tsx similarity index 85% rename from src/components/OrgSettings/ActionItemCategories/CategoryModal.test.tsx rename to src/components/OrgSettings/ActionItemCategories/CategoryModal.spec.tsx index 39d4884e8b..e4b5663788 100644 --- a/src/components/OrgSettings/ActionItemCategories/CategoryModal.test.tsx +++ b/src/components/OrgSettings/ActionItemCategories/CategoryModal.spec.tsx @@ -14,11 +14,23 @@ import { MOCKS, MOCKS_ERROR } from './OrgActionItemCategoryMocks'; import type { InterfaceActionItemCategoryModal } from './CategoryModal'; import CategoryModal from './CategoryModal'; import { toast } from 'react-toastify'; - -jest.mock('react-toastify', () => ({ +import { vi } from 'vitest'; +/** + * This file contains unit tests for the `CategoryModal` component. + * + * The tests cover: + * - Proper rendering of the component in various scenarios, including `create` and `edit` modes, mock data, and error states. + * - Handling user interactions with form fields, such as updating the category name and toggling the `isDisabled` switch. + * - Ensuring form submissions trigger appropriate callbacks (e.g., `refetchCategories` and `hide`) and display correct toast notifications. + * - Simulating GraphQL query and mutation operations with mocked data to validate behavior in success and error cases. + * - Testing edge cases, such as submitting without changes, invalid inputs, and handling API errors gracefully. + * - Verifying proper integration of internationalization, Redux state, routing, and toast notifications for success and error feedback. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -37,8 +49,8 @@ const translations = { const categoryProps: InterfaceActionItemCategoryModal[] = [ { isOpen: true, - hide: jest.fn(), - refetchCategories: jest.fn(), + hide: vi.fn(), + refetchCategories: vi.fn(), orgId: 'orgId', mode: 'create', category: { @@ -51,8 +63,8 @@ const categoryProps: InterfaceActionItemCategoryModal[] = [ }, { isOpen: true, - hide: jest.fn(), - refetchCategories: jest.fn(), + hide: vi.fn(), + refetchCategories: vi.fn(), orgId: 'orgId', mode: 'edit', category: { diff --git a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx similarity index 83% rename from src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx rename to src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx index 784d69325f..27eec94851 100644 --- a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx +++ b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx @@ -12,19 +12,36 @@ import i18n from 'utils/i18nForTest'; import type { ApolloLink } from '@apollo/client'; import { MOCKS, MOCKS_EMPTY, MOCKS_ERROR } from './OrgActionItemCategoryMocks'; import OrgActionItemCategories from './OrgActionItemCategories'; - -jest.mock('react-toastify', () => ({ +import { vi } from 'vitest'; + +/** + * This file contains unit tests for the `OrgActionItemCategories` component. + * + * The tests cover: + * - Proper rendering of the component under different conditions, including scenarios with populated categories, empty categories, and API errors. + * - User interactions such as searching, filtering, sorting categories, and opening/closing modals for creating or editing categories. + * - Verification of GraphQL query and mutation behaviors using mock data, ensuring correct functionality in both success and error cases. + * - Handling edge cases like no input, invalid input, and form resets. + * - Integration tests for Redux state, routing, internationalization, and toast notifications. + * - Ensuring sorting functionality reflects the `createdAt` property both in ascending and descending order. + * - Testing the modal interactions for creating and editing categories, ensuring proper lifecycle (open/close) and state updates. + * - Checking the rendering of error messages and placeholders when no data is available or an error occurs. + * - Validation of search functionality for categories by name, including clearing the search input and using keyboard shortcuts like `Enter`. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('@mui/x-date-pickers/DateTimePicker', () => { +vi.mock('@mui/x-date-pickers/DateTimePicker', async () => { + const dateTimePickerModule = await vi.importActual( + '@mui/x-date-pickers/DesktopDateTimePicker', + ); return { - DateTimePicker: jest.requireActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ).DesktopDateTimePicker, + DateTimePicker: dateTimePickerModule.DesktopDateTimePicker, }; }); diff --git a/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.test.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.spec.tsx similarity index 76% rename from src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.spec.tsx index da92dfd201..9b69a0e331 100644 --- a/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.test.tsx +++ b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.spec.tsx @@ -1,30 +1,40 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; - import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - import AgendaCategoryCreateModal from './AgendaCategoryCreateModal'; +import { vi } from 'vitest'; +/** + * This file contains unit tests for the `AgendaCategoryCreateModal` component. + * + * The tests cover: + * - Rendering of the modal, ensuring all elements such as form fields, buttons, and labels are displayed correctly. + * - Behavior of form inputs, including updating the `formState` when the `name` and `description` fields are changed. + * - Proper invocation of the `createAgendaCategoryHandler` when the form is submitted. + * - Integration of Redux state, routing, localization (i18n), and date-picker utilities to ensure compatibility and proper rendering. + * - Validations for form controls to check user interactions, including typing and submitting the form. + * - Mock function verifications for `setFormState`, `hideCreateModal`, and other handlers to ensure state changes and actions are triggered appropriately. + * - Handling edge cases, such as empty fields or invalid data, ensuring graceful degradation of functionality. + */ const mockFormState = { name: 'Test Name', description: 'Test Description', createdBy: 'Test User', }; -const mockHideCreateModal = jest.fn(); -const mockSetFormState = jest.fn(); -const mockCreateAgendaCategoryHandler = jest.fn(); +const mockHideCreateModal = vi.fn(); +const mockSetFormState = vi.fn(); +const mockCreateAgendaCategoryHandler = vi.fn(); const mockT = (key: string): string => key; describe('AgendaCategoryCreateModal', () => { - test('renders modal correctly', () => { + it('renders modal correctly', () => { render( @@ -54,7 +64,7 @@ describe('AgendaCategoryCreateModal', () => { screen.getByTestId('createAgendaCategoryModalCloseBtn'), ).toBeInTheDocument(); }); - test('tests the condition for formState.name and formState.description', () => { + it('tests the condition for formState.name and formState.description', () => { const mockFormState = { name: 'Test Name', description: 'Test Description', @@ -97,7 +107,7 @@ describe('AgendaCategoryCreateModal', () => { description: 'New description', }); }); - test('calls createAgendaCategoryHandler when form is submitted', () => { + it('calls createAgendaCategoryHandler when form is submitted', () => { render( diff --git a/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.test.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.spec.tsx similarity index 82% rename from src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.spec.tsx index 168b97abd3..8be982271c 100644 --- a/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.test.tsx +++ b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.spec.tsx @@ -7,24 +7,36 @@ import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; - import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - import AgendaCategoryUpdateModal from './AgendaCategoryUpdateModal'; +import { vi } from 'vitest'; + +/** + * Unit tests for `AgendaCategoryUpdateModal`: + * + * - **Rendering**: Verifies key elements (e.g., text, buttons) render correctly. + * - **Close Button**: Ensures `hideUpdateModal` is called on close. + * - **Form Inputs**: Confirms `setFormState` updates with new `name` and `description`. + * - **Submission**: Checks `updateAgendaCategoryHandler` triggers on submit. + * - **Integration**: Validates compatibility with Redux, routing, i18n, and MUI date-picker. + * - **Mocks**: Ensures handlers (`setFormState`, `hideUpdateModal`, `updateAgendaCategoryHandler`) are called with correct arguments. + * + * This suite ensures component reliability and behavior consistency. + */ const mockFormState = { name: 'Test Name', description: 'Test Description', createdBy: 'Test User', }; -const mockHideUpdateModal = jest.fn(); -const mockSetFormState = jest.fn(); -const mockUpdateAgendaCategoryHandler = jest.fn(); +const mockHideUpdateModal = vi.fn(); +const mockSetFormState = vi.fn(); +const mockUpdateAgendaCategoryHandler = vi.fn(); const mockT = (key: string): string => key; describe('AgendaCategoryUpdateModal', () => { - test('renders modal correctly', () => { + it('renders modal correctly', () => { render( @@ -53,7 +65,7 @@ describe('AgendaCategoryUpdateModal', () => { ).toBeInTheDocument(); }); - test('calls hideUpdateModal when close button is clicked', () => { + it('calls hideUpdateModal when close button is clicked', () => { render( @@ -79,7 +91,7 @@ describe('AgendaCategoryUpdateModal', () => { expect(mockHideUpdateModal).toHaveBeenCalledTimes(1); }); - test('tests the condition for formState.name and formState.description', () => { + it('tests the condition for formState.name and formState.description', () => { const mockFormState = { name: 'Test Name', description: 'Test Description', @@ -123,7 +135,7 @@ describe('AgendaCategoryUpdateModal', () => { }); }); - test('calls updateAgendaCategoryHandler when form is submitted', () => { + it('calls updateAgendaCategoryHandler when form is submitted', () => { render( diff --git a/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.spec.tsx similarity index 82% rename from src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.spec.tsx index 56cb450647..a2bd6d0130 100644 --- a/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx +++ b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.spec.tsx @@ -7,9 +7,7 @@ import { waitForElementToBeRemoved, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -22,23 +20,38 @@ import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import OrganizationAgendaCategory from './OrganizationAgendaCategory'; -import { - MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY, - MOCKS_ERROR_MUTATION, -} from './OrganizationAgendaCategoryErrorMocks'; +import { MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY } from './OrganizationAgendaCategoryErrorMocks'; import { MOCKS } from './OrganizationAgendaCategoryMocks'; - -jest.mock('react-toastify', () => ({ +import { vi } from 'vitest'; + +/** + * Unit Tests for `OrganizationAgendaCategory` Component + * + * - **Load Component**: Verifies successful rendering of key elements like `createAgendaCategory`. + * - **Error Handling**: Confirms error view appears when agenda category list query fails. + * - **Modal Functionality**: + * - Opens and closes the create agenda category modal. + * - Ensures `createAgendaCategoryModalCloseBtn` disappears on close. + * - **Create Agenda Category**: + * - Simulates filling the form and submission. + * - Verifies success toast on successful creation (`agendaCategoryCreated`). + * - **Integration**: Validates compatibility with Redux, Apollo, i18n, and MUI date-picker. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '123' }), -})); +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: '123' }), + }; +}); async function wait(ms = 100): Promise { await act(() => { @@ -53,8 +66,6 @@ const link2 = new StaticMockLink( MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY, true, ); -const link3 = new StaticMockLink(MOCKS_ERROR_MUTATION, true); - const translations = { ...JSON.parse( JSON.stringify( @@ -70,7 +81,7 @@ describe('Testing Agenda Categories Component', () => { description: 'Test Description', createdBy: 'Test User', }; - test('Component loads correctly', async () => { + it('Component loads correctly', async () => { const { getByText } = render( @@ -90,7 +101,7 @@ describe('Testing Agenda Categories Component', () => { }); }); - test('render error component on unsuccessful agenda category list query', async () => { + it('render error component on unsuccessful agenda category list query', async () => { const { queryByText } = render( @@ -112,7 +123,7 @@ describe('Testing Agenda Categories Component', () => { }); }); - test('opens and closes the create agenda category modal', async () => { + it('opens and closes the create agenda category modal', async () => { render( @@ -145,7 +156,7 @@ describe('Testing Agenda Categories Component', () => { screen.queryByTestId('createAgendaCategoryModalCloseBtn'), ); }); - test('creates new agenda cagtegory', async () => { + it('creates new agenda cagtegory', async () => { render( diff --git a/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.spec.tsx similarity index 80% rename from src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx rename to src/components/OrgSettings/General/DeleteOrg/DeleteOrg.spec.tsx index 77ffe65c08..e911d195dc 100644 --- a/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx +++ b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.spec.tsx @@ -1,11 +1,9 @@ import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { render, screen, waitFor } from '@testing-library/react'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; - import { DELETE_ORGANIZATION_MUTATION, REMOVE_SAMPLE_ORGANIZATION_MUTATION, @@ -17,6 +15,24 @@ import DeleteOrg from './DeleteOrg'; import { ToastContainer, toast } from 'react-toastify'; import { IS_SAMPLE_ORGANIZATION_QUERY } from 'GraphQl/Queries/Queries'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; + +/** + * Unit Tests for `DeleteOrg` Component + * + * - **Toggle Modal**: Verifies the ability to open and close the delete organization modal for both sample and non-sample organizations. + * - **Delete Organization**: + * - Simulates deleting a non-sample organization and ensures the correct GraphQL mutation is triggered. + * - Confirms navigation occurs after a sample organization is deleted. + * - **Error Handling**: + * - Handles errors from `DELETE_ORGANIZATION_MUTATION` and `IS_SAMPLE_ORGANIZATION_QUERY`. + * - Verifies `toast.error` is called with appropriate error messages when mutations fail. + * - **Mocks**: + * - Mocks GraphQL queries and mutations using `StaticMockLink` for different success and error scenarios. + * - Uses `useParams` to simulate URL parameters (`orgId`). + * - Mocks `useNavigate` to check navigation after successful deletion. + * - **Toast Notifications**: Ensures `toast.success` or `toast.error` is triggered based on success or failure of actions. + */ const { setItem } = useLocalStorage(); @@ -98,13 +114,16 @@ const MOCKS_WITH_ERROR = [ }, ]; -const mockNavgatePush = jest.fn(); +const mockNavgatePush = vi.fn(); let mockURL = '123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockURL }), - useNavigate: () => mockNavgatePush, -})); +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: mockURL }), + useNavigate: () => mockNavgatePush, + }; +}); const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_WITH_ERROR, true); @@ -114,7 +133,7 @@ afterEach(() => { }); describe('Delete Organization Component', () => { - test('should be able to Toggle Delete Organization Modal', async () => { + it('should be able to Toggle Delete Organization Modal', async () => { mockURL = '456'; setItem('SuperAdmin', true); await act(async () => { @@ -143,7 +162,7 @@ describe('Delete Organization Component', () => { }); }); - test('should be able to Toggle Delete Organization Modal When Organization is Sample Organization', async () => { + it('should be able to Toggle Delete Organization Modal When Organization is Sample Organization', async () => { mockURL = '123'; setItem('SuperAdmin', true); await act(async () => { @@ -173,7 +192,7 @@ describe('Delete Organization Component', () => { }); }); - test('Delete organization functionality should work properly', async () => { + it('Delete organization functionality should work properly', async () => { mockURL = '456'; setItem('SuperAdmin', true); await act(async () => { @@ -201,7 +220,7 @@ describe('Delete Organization Component', () => { }); }); - test('Delete organization functionality should work properly for sample org', async () => { + it('Delete organization functionality should work properly for sample org', async () => { mockURL = '123'; setItem('SuperAdmin', true); await act(async () => { @@ -234,10 +253,10 @@ describe('Delete Organization Component', () => { expect(mockNavgatePush).toHaveBeenCalledWith('/orglist'); }); - test('Error handling for IS_SAMPLE_ORGANIZATION_QUERY mock', async () => { + it('Error handling for IS_SAMPLE_ORGANIZATION_QUERY mock', async () => { mockURL = '123'; setItem('SuperAdmin', true); - jest.spyOn(toast, 'error'); + vi.spyOn(toast, 'error'); await act(async () => { render( @@ -270,10 +289,10 @@ describe('Delete Organization Component', () => { }); }); - test('Error handling for DELETE_ORGANIZATION_MUTATION mock', async () => { + it('Error handling for DELETE_ORGANIZATION_MUTATION mock', async () => { mockURL = '456'; setItem('SuperAdmin', true); - jest.spyOn(toast, 'error'); + vi.spyOn(toast, 'error'); await act(async () => { render( diff --git a/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.spec.tsx similarity index 86% rename from src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx rename to src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.spec.tsx index 8db8773381..45bdfbc122 100644 --- a/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx +++ b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.spec.tsx @@ -12,6 +12,18 @@ import { } from 'GraphQl/Mutations/mutations'; import { ORGANIZATION_CUSTOM_FIELDS } from 'GraphQl/Queries/Queries'; import { ToastContainer, toast } from 'react-toastify'; +import { vi } from 'vitest'; + +/** + * Unit Tests for `OrgProfileFieldSettings` Component + * + * - Saving Custom Field: Verifies success and failure of adding a custom field. + * - Typing Custom Field Name: Ensures input updates correctly. + * - Handling No Custom Fields: Displays message when no custom fields exist. + * - Removing Custom Field: Verifies success and failure of removing a custom field. + * - Error Handling: Ensures error messages for GraphQL mutations are displayed. + * - Mock GraphQL Responses: Mocks GraphQL queries and mutations for different scenarios. + */ const MOCKS = [ { @@ -161,7 +173,7 @@ async function wait(ms = 100): Promise { } describe('Testing Save Button', () => { - test('Testing Failure Case For Fetching Custom field', async () => { + it('Testing Failure Case For Fetching Custom field', async () => { render( { screen.queryByText('Failed to fetch custom field'), ).toBeInTheDocument(); }); - test('Saving Organization Custom Field', async () => { + it('Saving Organization Custom Field', async () => { render( @@ -195,7 +207,7 @@ describe('Testing Save Button', () => { expect(screen.queryByText('Field added successfully')).toBeInTheDocument(); }); - test('Testing Failure Case For Saving Custom Field', async () => { + it('Testing Failure Case For Saving Custom Field', async () => { render( @@ -218,7 +230,7 @@ describe('Testing Save Button', () => { ).toBeInTheDocument(); }); - test('Testing Typing Organization Custom Field Name', async () => { + it('Testing Typing Organization Custom Field Name', async () => { const { getByTestId } = render( @@ -232,7 +244,7 @@ describe('Testing Save Button', () => { const fieldNameInput = getByTestId('customFieldInput'); userEvent.type(fieldNameInput, 'Age'); }); - test('When No Custom Data is Present', async () => { + it('When No Custom Data is Present', async () => { const { getByText } = render( @@ -244,7 +256,7 @@ describe('Testing Save Button', () => { await wait(); expect(getByText('No custom fields available')).toBeInTheDocument(); }); - test('Testing Remove Custom Field Button', async () => { + it('Testing Remove Custom Field Button', async () => { render( @@ -262,8 +274,8 @@ describe('Testing Save Button', () => { ).toBeInTheDocument(); }); - test('Testing Failure Case For Removing Custom Field', async () => { - const toastSpy = jest.spyOn(toast, 'error'); + it('Testing Failure Case For Removing Custom Field', async () => { + const toastSpy = vi.spyOn(toast, 'error'); render( diff --git a/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.spec.tsx similarity index 90% rename from src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx rename to src/components/OrgSettings/General/OrgUpdate/OrgUpdate.spec.tsx index 6304bb3ec9..2a6496d69a 100644 --- a/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx +++ b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.spec.tsx @@ -11,6 +11,18 @@ import { MOCKS_ERROR_ORGLIST, MOCKS_ERROR_UPDATE_ORGLIST, } from './OrgUpdateMocks'; +import { vi } from 'vitest'; + +/** + * Unit Tests for `OrgUpdate` Component + * + * - Rendering Component with Props: Verifies if labels and input fields are correctly rendered based on mock data. + * - Updating Organization: Ensures the form updates with new data and saves changes correctly. + * - Error Handling: Verifies error messages when organization cannot be found or updated. + * - Toast on Error: Verifies that an error toast is shown when the update fails. + * - Form Field Values: Ensures form values are correctly displayed and updated. + * - GraphQL Mock Responses: Mocks GraphQL responses for success and error scenarios. + */ const link = new StaticMockLink(MOCKS, true); @@ -45,9 +57,9 @@ describe('Testing Organization Update', () => { isVisible: true, }; - global.alert = jest.fn(); + global.alert = vi.fn(); - test('should render props and text elements test for the page component along with mock data', async () => { + it('should render props and text elements test for the page component along with mock data', async () => { act(() => { render( @@ -95,7 +107,7 @@ describe('Testing Organization Update', () => { expect(isVisible).not.toBeChecked(); }); - test('Should Update organization properly', async () => { + it('Should Update organization properly', async () => { await act(async () => { render( @@ -168,7 +180,7 @@ describe('Testing Organization Update', () => { expect(isVisible).toBeChecked(); }); - test('Should render error occured text when Organization Could not be found', async () => { + it('Should render error occured text when Organization Could not be found', async () => { act(() => { render( @@ -182,7 +194,7 @@ describe('Testing Organization Update', () => { expect(screen.getByText(/Mock Graphql Error/i)).toBeInTheDocument(); }); - test('Should show error occured toast when Organization could not be updated', async () => { + it('Should show error occured toast when Organization could not be updated', async () => { await act(async () => { render( diff --git a/src/components/OrganizationCard/OrganizationCard.test.tsx b/src/components/OrganizationCard/OrganizationCard.spec.tsx similarity index 66% rename from src/components/OrganizationCard/OrganizationCard.test.tsx rename to src/components/OrganizationCard/OrganizationCard.spec.tsx index e4abf3a1fd..c557253d59 100644 --- a/src/components/OrganizationCard/OrganizationCard.test.tsx +++ b/src/components/OrganizationCard/OrganizationCard.spec.tsx @@ -2,8 +2,18 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import OrganizationCard from './OrganizationCard'; +/** + * This file contains unit tests for the `OrganizationCard` component. + * + * The tests cover: + * - Rendering the component with all provided props and verifying the correct display of text elements. + * - Ensuring the component handles cases where certain props (like image) are not provided. + * + * These tests utilize the React Testing Library for rendering and querying DOM elements. + */ + describe('Testing the Organization Card', () => { - test('should render props and text elements test for the page component', () => { + it('should render props and text elements test for the page component', () => { const props = { id: '123', image: 'https://via.placeholder.com/80', @@ -20,7 +30,7 @@ describe('Testing the Organization Card', () => { expect(screen.getByText(props.lastName)).toBeInTheDocument(); }); - test('Should render text elements when props value is not passed', () => { + it('Should render text elements when props value is not passed', () => { const props = { id: '123', image: '', diff --git a/src/components/OrganizationCardStart/OrganizationCardStart.test.tsx b/src/components/OrganizationCardStart/OrganizationCardStart.spec.tsx similarity index 76% rename from src/components/OrganizationCardStart/OrganizationCardStart.test.tsx rename to src/components/OrganizationCardStart/OrganizationCardStart.spec.tsx index dd65c8649e..71263fb65b 100644 --- a/src/components/OrganizationCardStart/OrganizationCardStart.test.tsx +++ b/src/components/OrganizationCardStart/OrganizationCardStart.spec.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import OrganizationCardStart from './OrganizationCardStart'; +import { describe, expect } from 'vitest'; describe('Testing the Organization Cards', () => { - test('should render props and text elements test for the page component', () => { + it('should render props and text elements test for the page component', () => { const props = { id: '123', image: 'https://via.placeholder.com/80', @@ -15,7 +16,7 @@ describe('Testing the Organization Cards', () => { expect(screen.getByText(props.name)).toBeInTheDocument(); }); - test('Should render text elements when props value is not passed', () => { + it('Should render text elements when props value is not passed', () => { const props = { id: '123', image: '', diff --git a/src/components/OrganizationDashCards/CardItem.module.css b/src/components/OrganizationDashCards/CardItem.module.css deleted file mode 100644 index bfb85cb1bb..0000000000 --- a/src/components/OrganizationDashCards/CardItem.module.css +++ /dev/null @@ -1,81 +0,0 @@ -.cardItem { - position: relative; - display: flex; - align-items: center; - border: 1px solid var(--bs-gray-200); - border-radius: 8px; - margin-top: 20px; -} - -.cardItem .iconWrapper { - position: relative; - height: 40px; - width: 40px; - display: flex; - justify-content: center; - align-items: center; -} - -.cardItem .iconWrapper .themeOverlay { - background: var(--bs-primary); - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - opacity: 0.12; - border-radius: 50%; -} - -.cardItem .iconWrapper .dangerOverlay { - background: var(--bs-danger); - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - opacity: 0.12; - border-radius: 50%; -} - -.cardItem .title { - font-size: 1rem; - flex: 1; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 1; - line-clamp: 1; - -webkit-box-orient: vertical; - margin-left: 3px; -} - -.cardItem .location { - font-size: 0.9rem; - color: var(--bs-primary); - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 1; - line-clamp: 1; - -webkit-box-orient: vertical; -} - -.cardItem .time { - font-size: 0.9rem; - color: var(--bs-secondary); -} - -.cardItem .creator { - font-size: 1rem; - color: rgb(33, 208, 21); -} - -.rightCard { - display: flex; - gap: 7px; - min-width: 170px; - justify-content: center; - flex-direction: column; - margin-left: 10px; - overflow-x: hidden; - width: 210px; -} diff --git a/src/components/OrganizationDashCards/CardItem.test.tsx b/src/components/OrganizationDashCards/CardItem.spec.tsx similarity index 98% rename from src/components/OrganizationDashCards/CardItem.test.tsx rename to src/components/OrganizationDashCards/CardItem.spec.tsx index 31f3474607..71ff22774b 100644 --- a/src/components/OrganizationDashCards/CardItem.test.tsx +++ b/src/components/OrganizationDashCards/CardItem.spec.tsx @@ -1,8 +1,8 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import CardItem from './CardItem'; import type { InterfaceCardItem } from './CardItem'; import dayjs from 'dayjs'; +import React from 'react'; describe('Testing the Organization Card', () => { test('Should render props and text elements For event card', () => { @@ -36,7 +36,6 @@ describe('Testing the Organization Card', () => { email: 'johndoe@example.com', firstName: 'John', lastName: 'Doe', - __typename: 'User', _id: '1', }, }; diff --git a/src/components/OrganizationDashCards/CardItem.tsx b/src/components/OrganizationDashCards/CardItem.tsx index a7cfaa0f57..24e8182913 100644 --- a/src/components/OrganizationDashCards/CardItem.tsx +++ b/src/components/OrganizationDashCards/CardItem.tsx @@ -5,7 +5,7 @@ import MarkerIcon from 'assets/svgs/cardItemLocation.svg?react'; import DateIcon from 'assets/svgs/cardItemDate.svg?react'; import UserIcon from 'assets/svgs/user.svg?react'; import dayjs from 'dayjs'; -import styles from './CardItem.module.css'; +import styles from '../../style/app.module.css'; import { PersonAddAlt1Rounded } from '@mui/icons-material'; /** @@ -17,7 +17,12 @@ export interface InterfaceCardItem { time?: string; startdate?: string; enddate?: string; - creator?: any; + creator?: { + _id: string; + firstName: string; + lastName: string; + email: string; + }; location?: string; } @@ -27,7 +32,7 @@ export interface InterfaceCardItem { * @param props - Props for the CardItem component. * @returns JSX element representing the card item. */ -const cardItem = (props: InterfaceCardItem): JSX.Element => { +const CardItem = (props: InterfaceCardItem): JSX.Element => { const { creator, type, title, startdate, time, enddate, location } = props; return ( <> @@ -120,4 +125,4 @@ const cardItem = (props: InterfaceCardItem): JSX.Element => { ); }; -export default cardItem; +export default CardItem; diff --git a/src/components/OrganizationDashCards/CardItemLoading.spec.tsx b/src/components/OrganizationDashCards/CardItemLoading.spec.tsx new file mode 100644 index 0000000000..8a132657a7 --- /dev/null +++ b/src/components/OrganizationDashCards/CardItemLoading.spec.tsx @@ -0,0 +1,23 @@ +import CardItemLoading from './CardItemLoading'; +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import styles from '../../style/app.module.css'; +describe('Test the CardItemLoading component', () => { + test('Should render the component', () => { + render(); + expect(screen.getByTestId('cardItemLoading')).toBeInTheDocument(); + }); + + test('Should render all required child elements', () => { + render(); + + const cardItemLoading = screen.getByTestId('cardItemLoading'); + expect(cardItemLoading).toBeInTheDocument(); + + const iconWrapper = cardItemLoading.querySelector(`.${styles.iconWrapper}`); + expect(iconWrapper).toBeInTheDocument(); + + const title = cardItemLoading.querySelector(`.${styles.title}`); + expect(title).toBeInTheDocument(); + }); +}); diff --git a/src/components/OrganizationDashCards/CardItemLoading.tsx b/src/components/OrganizationDashCards/CardItemLoading.tsx index c7c666afb6..9db7c333c8 100644 --- a/src/components/OrganizationDashCards/CardItemLoading.tsx +++ b/src/components/OrganizationDashCards/CardItemLoading.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import styles from './CardItem.module.css'; +import styles from '../../style/app.module.css'; /** * CardItemLoading component is a loading state for the card item. It is used when the data is being fetched. * @returns JSX.Element */ -const cardItemLoading = (): JSX.Element => { +const CardItemLoading = (): JSX.Element => { return ( <>
{ ); }; -export default cardItemLoading; +export default CardItemLoading; diff --git a/src/components/OrganizationDashCards/DashBoardCardLoading.spec.tsx b/src/components/OrganizationDashCards/DashBoardCardLoading.spec.tsx new file mode 100644 index 0000000000..f1cb2e56fa --- /dev/null +++ b/src/components/OrganizationDashCards/DashBoardCardLoading.spec.tsx @@ -0,0 +1,28 @@ +import DashBoardCardLoading from './DashboardCardLoading'; +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import styles from '../../style/app.module.css'; +describe('Testing the DashBoardCardLoading component', () => { + beforeEach(() => { + render(); + }); + test('should render the component', () => { + expect(screen.getByTestId('Card')).toBeInTheDocument(); + }); + + test('should render every children elements of the component', () => { + const Card = screen.queryByTestId('Card'); + const CardBody = Card?.querySelector(`.${styles.cardBody}`); + expect(CardBody).toBeInTheDocument(); + const iconWrapper = Card?.querySelector(`.${styles.iconWrapper}`); + expect(iconWrapper).toBeInTheDocument(); + const themeOverlay = Card?.querySelector(`.${styles.themeOverlay}`); + expect(themeOverlay).toBeInTheDocument(); + const textWrapper = Card?.querySelector(`.${styles.textWrapper}`); + expect(textWrapper).toBeInTheDocument(); + const primaryText = Card?.querySelector(`.${styles.primaryText}`); + expect(primaryText).toBeInTheDocument(); + const secondaryText = Card?.querySelector(`.${styles.secondaryText}`); + expect(secondaryText).toBeInTheDocument(); + }); +}); diff --git a/src/components/OrganizationDashCards/DashboardCard.test.tsx b/src/components/OrganizationDashCards/DashboardCard.spec.tsx similarity index 99% rename from src/components/OrganizationDashCards/DashboardCard.test.tsx rename to src/components/OrganizationDashCards/DashboardCard.spec.tsx index 71e5e1fed0..50a47f69d5 100644 --- a/src/components/OrganizationDashCards/DashboardCard.test.tsx +++ b/src/components/OrganizationDashCards/DashboardCard.spec.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import DashboardCard from './DashboardCard'; - +import React from 'react'; describe('Testing the Dashboard Card', () => { test('should render props and text elements For event card', () => { const props = { diff --git a/src/components/OrganizationDashCards/DashboardCard.tsx b/src/components/OrganizationDashCards/DashboardCard.tsx index 4a29eafc57..0f0222ba29 100644 --- a/src/components/OrganizationDashCards/DashboardCard.tsx +++ b/src/components/OrganizationDashCards/DashboardCard.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Card, Row } from 'react-bootstrap'; import Col from 'react-bootstrap/Col'; -import styles from './Dashboardcard.module.css'; +import styles from '../../style/app.module.css'; /** Dashboard card component is used to display the card with icon, title and count. * @param icon - Icon for the card diff --git a/src/components/OrganizationDashCards/DashboardCardLoading.tsx b/src/components/OrganizationDashCards/DashboardCardLoading.tsx index b37c1e2065..4882e906d2 100644 --- a/src/components/OrganizationDashCards/DashboardCardLoading.tsx +++ b/src/components/OrganizationDashCards/DashboardCardLoading.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { Card, Row } from 'react-bootstrap'; import Col from 'react-bootstrap/Col'; -import styles from './Dashboardcard.module.css'; +import styles from '../../style/app.module.css'; /** * Dashboard card loading component is a loading state for the dashboard card. It is used when the data is being fetched. * @returns JSX.Element */ -const dashBoardCardLoading = (): JSX.Element => { +const DashBoardCardLoading = (): JSX.Element => { return ( - + @@ -37,4 +37,4 @@ const dashBoardCardLoading = (): JSX.Element => { ); }; -export default dashBoardCardLoading; +export default DashBoardCardLoading; diff --git a/src/components/OrganizationDashCards/Dashboardcard.module.css b/src/components/OrganizationDashCards/Dashboardcard.module.css deleted file mode 100644 index 365657fb4f..0000000000 --- a/src/components/OrganizationDashCards/Dashboardcard.module.css +++ /dev/null @@ -1,60 +0,0 @@ -.cardBody { - padding: 1.25rem 1.5rem; -} - -.cardBody .iconWrapper { - position: relative; - height: 48px; - width: 48px; - display: flex; - justify-content: center; - align-items: center; -} - -.cardBody .iconWrapper .themeOverlay { - background: var(--bs-primary); - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - opacity: 0.12; - border-radius: 50%; -} - -.cardBody .textWrapper .primaryText { - font-size: 24px; - font-weight: bold; - display: block; -} - -.cardBody .textWrapper .secondaryText { - font-size: 14px; - display: block; - color: var(--bs-secondary); -} - -@media (max-width: 600px) { - .cardBody { - min-height: 120px; - } - - .cardBody .iconWrapper { - position: absolute; - top: 1rem; - left: 1rem; - } - - .cardBody .textWrapper { - margin-top: calc(0.5rem + 36px); - text-align: right; - } - - .cardBody .textWrapper .primaryText { - font-size: 1.5rem; - } - - .cardBody .textWrapper .secondaryText { - font-size: 1rem; - } -} diff --git a/src/components/OrganizationScreen/OrganizationScreen.test.tsx b/src/components/OrganizationScreen/OrganizationScreen.spec.tsx similarity index 94% rename from src/components/OrganizationScreen/OrganizationScreen.test.tsx rename to src/components/OrganizationScreen/OrganizationScreen.spec.tsx index bc9aef388d..e6a75c46d8 100644 --- a/src/components/OrganizationScreen/OrganizationScreen.test.tsx +++ b/src/components/OrganizationScreen/OrganizationScreen.spec.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; -import 'jest-location-mock'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; @@ -11,10 +10,10 @@ import OrganizationScreen from './OrganizationScreen'; import { ORGANIZATION_EVENT_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; import styles from './OrganizationScreen.module.css'; - +import { vi } from 'vitest'; const mockID: string | undefined = '123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), useParams: () => ({ orgId: mockID }), useMatch: () => ({ params: { eventId: 'event123', orgId: '123' } }), })); @@ -80,7 +79,6 @@ describe('Testing OrganizationScreen', () => { const closeButton = screen.getByTestId('closeMenu'); fireEvent.click(closeButton); - // Check for contract class after closing expect(screen.getByTestId('mainpageright')).toHaveClass(styles.expand); const openButton = screen.getByTestId('openMenu'); @@ -92,10 +90,8 @@ describe('Testing OrganizationScreen', () => { test('handles window resize', () => { renderComponent(); - window.innerWidth = 800; fireEvent(window, new Event('resize')); - expect(screen.getByTestId('mainpageright')).toHaveClass(styles.expand); }); }); diff --git a/src/components/Pagination/Pagination.test.tsx b/src/components/Pagination/Pagination.spec.tsx similarity index 89% rename from src/components/Pagination/Pagination.test.tsx rename to src/components/Pagination/Pagination.spec.tsx index 40ac2ed19a..873f790565 100644 --- a/src/components/Pagination/Pagination.test.tsx +++ b/src/components/Pagination/Pagination.spec.tsx @@ -6,6 +6,7 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import Pagination from './Pagination'; import { store } from 'state/store'; import userEvent from '@testing-library/user-event'; +import { describe, it } from 'vitest'; describe('Testing Pagination component', () => { const props = { @@ -17,7 +18,7 @@ describe('Testing Pagination component', () => { }, }; - test('Component should be rendered properly on rtl', async () => { + it('Component should be rendered properly on rtl', async () => { render( @@ -42,7 +43,7 @@ const props = { theme: { direction: 'rtl' }, }; -test('Component should be rendered properly', async () => { +it('Component should be rendered properly', async () => { const theme = createTheme({ direction: 'rtl', }); diff --git a/src/components/ProfileDropdown/ProfileDropdown.test.tsx b/src/components/ProfileDropdown/ProfileDropdown.spec.tsx similarity index 52% rename from src/components/ProfileDropdown/ProfileDropdown.test.tsx rename to src/components/ProfileDropdown/ProfileDropdown.spec.tsx index 785f33ee92..06883768e6 100644 --- a/src/components/ProfileDropdown/ProfileDropdown.test.tsx +++ b/src/components/ProfileDropdown/ProfileDropdown.spec.tsx @@ -1,17 +1,29 @@ import React, { act } from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BrowserRouter } from 'react-router-dom'; import ProfileDropdown from './ProfileDropdown'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/react-testing'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import { GET_COMMUNITY_SESSION_TIMEOUT_DATA } from 'GraphQl/Queries/Queries'; +import { vi } from 'vitest'; const { setItem } = useLocalStorage(); + +const mockNavigate = vi.fn(); + +// Mock useNavigate hook +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useNavigate: () => mockNavigate, + }; +}); + const MOCKS = [ { request: { @@ -38,13 +50,13 @@ const MOCKS = [ }, ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, - clear: jest.fn(), + clear: vi.fn(), })); beforeEach(() => { @@ -60,11 +72,11 @@ beforeEach(() => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); localStorage.clear(); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); localStorage.clear(); }); @@ -130,19 +142,124 @@ describe('ProfileDropdown Component', () => { describe('Member screen routing testing', () => { test('member screen', async () => { + setItem('SuperAdmin', false); + setItem('AdminFor', []); + render( - + + + , ); + await act(async () => { userEvent.click(screen.getByTestId('togDrop')); }); + await act(async () => { + userEvent.click(screen.getByTestId('profileBtn')); + }); + + expect(mockNavigate).toHaveBeenCalledWith('/user/settings'); + }); + }); + + test('handles error when revoking refresh token during logout', async () => { + // Mock the revokeRefreshToken mutation to throw an error + const MOCKS_WITH_ERROR = [ + { + request: { + query: REVOKE_REFRESH_TOKEN, + }, + error: new Error('Failed to revoke refresh token'), + }, + ]; + + const consoleErrorMock = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + render( + + + + + , + ); + + // Open the dropdown + await act(async () => { + userEvent.click(screen.getByTestId('togDrop')); + }); + + // Click the logout button + await act(async () => { + userEvent.click(screen.getByTestId('logoutBtn')); + }); + + // Wait for any pending promises + await waitFor(() => { + // Assert that console.error was called + expect(consoleErrorMock).toHaveBeenCalledWith( + 'Error revoking refresh token:', + expect.any(Error), + ); + }); + + // Cleanup mock + consoleErrorMock.mockRestore(); + }); + + test('navigates to /user/settings for a user', async () => { + setItem('SuperAdmin', false); + setItem('AdminFor', []); + + render( + + + + + + + , + ); + + await act(async () => { + userEvent.click(screen.getByTestId('togDrop')); + }); + + await act(async () => { userEvent.click(screen.getByTestId('profileBtn')); - expect(global.window.location.pathname).toBe('/user/settings'); }); + + expect(mockNavigate).toHaveBeenCalledWith('/user/settings'); + }); + + test('navigates to /member/:userID for non-user roles', async () => { + setItem('SuperAdmin', true); // Set as admin + setItem('id', '123'); + + render( + + + + + + + , + ); + + await act(async () => { + userEvent.click(screen.getByTestId('togDrop')); + }); + + await act(async () => { + userEvent.click(screen.getByTestId('profileBtn')); + }); + + expect(mockNavigate).toHaveBeenCalledWith('/member/123'); }); }); diff --git a/src/components/RecurrenceOptions/CustomRecurrence.test.tsx b/src/components/RecurrenceOptions/CustomRecurrence.spec.tsx similarity index 98% rename from src/components/RecurrenceOptions/CustomRecurrence.test.tsx rename to src/components/RecurrenceOptions/CustomRecurrence.spec.tsx index fc0cacf5c4..236e34e855 100644 --- a/src/components/RecurrenceOptions/CustomRecurrence.test.tsx +++ b/src/components/RecurrenceOptions/CustomRecurrence.spec.tsx @@ -9,7 +9,6 @@ import { } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import OrganizationEvents from '../../screens/OrganizationEvents/OrganizationEvents'; @@ -23,6 +22,7 @@ import { ThemeProvider } from 'react-bootstrap'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { MOCKS } from '../../screens/OrganizationEvents/OrganizationEventsMocks'; +import { describe, test, expect, vi } from 'vitest'; const theme = createTheme({ palette: { @@ -48,19 +48,20 @@ const translations = JSON.parse( ), ); -jest.mock('@mui/x-date-pickers/DateTimePicker', () => { +vi.mock('@mui/x-date-pickers/DateTimePicker', async () => { + const actual = await vi.importActual( + '@mui/x-date-pickers/DesktopDateTimePicker', + ); return { - DateTimePicker: jest.requireActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ).DesktopDateTimePicker, + DateTimePicker: actual.DesktopDateTimePicker, }; }); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warning: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warning: vi.fn(), + error: vi.fn(), }, })); diff --git a/src/components/RecurrenceOptions/CustomRecurrenceModal.module.css b/src/components/RecurrenceOptions/CustomRecurrenceModal.module.css deleted file mode 100644 index 5fe5d33c47..0000000000 --- a/src/components/RecurrenceOptions/CustomRecurrenceModal.module.css +++ /dev/null @@ -1,59 +0,0 @@ -.titlemodal { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 65%; -} - -.recurrenceRuleNumberInput { - width: 70px; -} - -.recurrenceRuleDateBox { - width: 70%; -} - -.recurrenceDayButton { - width: 33px; - height: 33px; - border: 1px solid var(--bs-gray); - cursor: pointer; - transition: background-color 0.3s; - display: inline-flex; - justify-content: center; - align-items: center; - margin-right: 0.5rem; - border-radius: 50%; -} - -.recurrenceDayButton:hover { - background-color: var(--bs-gray); -} - -.recurrenceDayButton.selected { - background-color: var(--bs-primary); - border-color: var(--bs-primary); - color: var(--bs-white); -} - -.recurrenceDayButton span { - color: var(--bs-gray); - padding: 0.25rem; - text-align: center; -} - -.recurrenceDayButton:hover span { - color: var(--bs-white); -} - -.recurrenceDayButton.selected span { - color: var(--bs-white); -} - -.recurrenceRuleSubmitBtn { - margin-left: 82%; - padding: 7px 15px; -} diff --git a/src/components/RecurrenceOptions/CustomRecurrenceModal.tsx b/src/components/RecurrenceOptions/CustomRecurrenceModal.tsx index deb1a03dab..aa85cc44ed 100644 --- a/src/components/RecurrenceOptions/CustomRecurrenceModal.tsx +++ b/src/components/RecurrenceOptions/CustomRecurrenceModal.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Button, Dropdown, Form, FormControl, Modal } from 'react-bootstrap'; -import styles from './CustomRecurrenceModal.module.css'; +import styles from '../../style/app.module.css'; import { DatePicker } from '@mui/x-date-pickers'; import { Days, @@ -153,7 +153,9 @@ const CustomRecurrenceModal: React.FC = ({ centered > -

{t('customRecurrence')}

+

+ {t('customRecurrence')} +

)} + {attachment && ( +
+ attachment + + +
+ )} + +
); } diff --git a/src/components/UserPortal/CommentCard/CommentCard.test.tsx b/src/components/UserPortal/CommentCard/CommentCard.spec.tsx similarity index 85% rename from src/components/UserPortal/CommentCard/CommentCard.test.tsx rename to src/components/UserPortal/CommentCard/CommentCard.spec.tsx index f02e5a606a..112fd1bcf5 100644 --- a/src/components/UserPortal/CommentCard/CommentCard.test.tsx +++ b/src/components/UserPortal/CommentCard/CommentCard.spec.tsx @@ -7,12 +7,23 @@ import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; - import CommentCard from './CommentCard'; import userEvent from '@testing-library/user-event'; import { LIKE_COMMENT, UNLIKE_COMMENT } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; - +import { vi } from 'vitest'; + +/** + * Unit tests for the CommentCard component. + * + * These tests ensure the CommentCard component renders and behaves as expected + * under different scenarios. They cover various functionalities like: + * - Initial rendering with comment liked/not liked by user + * - User liking a comment + * - User unliking a comment + * Mocked dependencies like `useLocalStorage` and Apollo Client mocks are used + * to isolate the component and test its behavior independently. + */ const { getItem, setItem } = useLocalStorage(); async function wait(ms = 100): Promise { @@ -56,8 +67,8 @@ const MOCKS = [ }, ]; -const handleLikeComment = jest.fn(); -const handleDislikeComment = jest.fn(); +const handleLikeComment = vi.fn(); +const handleDislikeComment = vi.fn(); const link = new StaticMockLink(MOCKS, true); describe('Testing CommentCard Component [User Portal]', () => { @@ -67,7 +78,7 @@ describe('Testing CommentCard Component [User Portal]', () => { }); }); - test('Component should be rendered properly if comment is already liked by the user.', async () => { + it('Component should be rendered properly if comment is already liked by the user.', async () => { const cardProps = { id: '1', creator: { @@ -108,7 +119,7 @@ describe('Testing CommentCard Component [User Portal]', () => { } }); - test('Component should be rendered properly if comment is not already liked by the user.', async () => { + it('Component should be rendered properly if comment is not already liked by the user.', async () => { const cardProps = { id: '1', creator: { @@ -149,7 +160,7 @@ describe('Testing CommentCard Component [User Portal]', () => { } }); - test('Component renders as expected if user likes the comment.', async () => { + it('Component renders as expected if user likes the comment.', async () => { const cardProps = { id: '1', creator: { @@ -195,7 +206,7 @@ describe('Testing CommentCard Component [User Portal]', () => { } }); - test('Component renders as expected if user unlikes the comment.', async () => { + it('Component renders as expected if user unlikes the comment.', async () => { const cardProps = { id: '1', creator: { diff --git a/src/components/UserPortal/ContactCard/ContactCard.module.css b/src/components/UserPortal/ContactCard/ContactCard.module.css index 19d66596c9..59d857efe2 100644 --- a/src/components/UserPortal/ContactCard/ContactCard.module.css +++ b/src/components/UserPortal/ContactCard/ContactCard.module.css @@ -10,8 +10,8 @@ .contactImage { width: 45px !important; - height: auto !important; - border-radius: 10px; + height: 45px !important; + border-radius: 100%; display: flex; align-items: center; justify-content: center; @@ -19,9 +19,10 @@ .contactNameContainer { display: flex; - flex-direction: column; padding: 0px 10px; - justify-content: center; + justify-content: space-between; + align-items: center; + flex-grow: 1; } .grey { @@ -35,3 +36,13 @@ .bgWhite { background-color: white; } + +.lastMessage { + margin-bottom: 0; + font-size: 14px; + color: rgba(163, 163, 163, 0.839); +} + +.unseenMessagesCount { + border-radius: 50%; +} diff --git a/src/components/UserPortal/ContactCard/ContactCard.test.tsx b/src/components/UserPortal/ContactCard/ContactCard.spec.tsx similarity index 72% rename from src/components/UserPortal/ContactCard/ContactCard.test.tsx rename to src/components/UserPortal/ContactCard/ContactCard.spec.tsx index 05a6a0a530..572a38feb7 100644 --- a/src/components/UserPortal/ContactCard/ContactCard.test.tsx +++ b/src/components/UserPortal/ContactCard/ContactCard.spec.tsx @@ -10,7 +10,19 @@ import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import ContactCard from './ContactCard'; import userEvent from '@testing-library/user-event'; - +import { vi } from 'vitest'; + +/** + * Unit tests for the ContactCard component. + * + * These tests ensure the ContactCard component renders and behaves as expected + * under different scenarios. They cover various functionalities like: + * - Rendering the contact card with and without a profile image + * - Selecting a contact by clicking on the card + * - Applying a grey background color to the selected contact card (for groups) + * Mocked dependencies like StaticMockLink are used + * to isolate the component and test its behavior independently. + */ const link = new StaticMockLink([], true); async function wait(ms = 100): Promise { @@ -30,12 +42,14 @@ let props = { image: '', selectedContact: '', type: '', - setSelectedContact: jest.fn(), - setSelectedChatType: jest.fn(), + unseenMessages: 2, + lastMessage: '', + setSelectedContact: vi.fn(), + setSelectedChatType: vi.fn(), }; describe('Testing ContactCard Component [User Portal]', () => { - test('Component should be rendered properly if person image is undefined', async () => { + it('Component should be rendered properly if person image is undefined', async () => { render( @@ -51,7 +65,7 @@ describe('Testing ContactCard Component [User Portal]', () => { await wait(); }); - test('Component should be rendered properly if person image is not undefined', async () => { + it('Component should be rendered properly if person image is not undefined', async () => { props = { ...props, image: 'personImage', @@ -72,7 +86,7 @@ describe('Testing ContactCard Component [User Portal]', () => { await wait(); }); - test('Contact gets selectected when component is clicked', async () => { + it('Contact gets selectected when component is clicked', async () => { render( @@ -92,7 +106,7 @@ describe('Testing ContactCard Component [User Portal]', () => { await wait(); }); - test('Component is rendered with background color grey if the contact is selected', async () => { + it('Component is rendered with background color grey if the contact is selected', async () => { props = { ...props, selectedContact: '1', diff --git a/src/components/UserPortal/ContactCard/ContactCard.tsx b/src/components/UserPortal/ContactCard/ContactCard.tsx index ef6bf2c9d5..5ec7c9c1d3 100644 --- a/src/components/UserPortal/ContactCard/ContactCard.tsx +++ b/src/components/UserPortal/ContactCard/ContactCard.tsx @@ -1,6 +1,7 @@ import React from 'react'; import styles from './ContactCard.module.css'; import Avatar from 'components/Avatar/Avatar'; +import { Badge } from 'react-bootstrap'; interface InterfaceContactCardProps { id: string; @@ -9,6 +10,8 @@ interface InterfaceContactCardProps { selectedContact: string; setSelectedContact: React.Dispatch>; isGroup: boolean; + unseenMessages: number; + lastMessage: string; } /** @@ -66,7 +69,15 @@ function contactCard(props: InterfaceContactCardProps): JSX.Element { /> )}
- {props.title} +
+ {props.title}{' '} +

{props.lastMessage}

+
+ {!!props.unseenMessages && ( + + {props.unseenMessages} + + )}
diff --git a/src/components/UserPortal/CreateDirectChat/CreateDirectChat.module.css b/src/components/UserPortal/CreateDirectChat/CreateDirectChat.module.css deleted file mode 100644 index 3795e402fa..0000000000 --- a/src/components/UserPortal/CreateDirectChat/CreateDirectChat.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.userData { - height: 400px; - overflow-y: scroll; - overflow-x: hidden !important; -} - -.modalContent { - width: 530px; -} diff --git a/src/components/UserPortal/CreateDirectChat/CreateDirectChat.spec.tsx b/src/components/UserPortal/CreateDirectChat/CreateDirectChat.spec.tsx new file mode 100644 index 0000000000..ccf23a882b --- /dev/null +++ b/src/components/UserPortal/CreateDirectChat/CreateDirectChat.spec.tsx @@ -0,0 +1,4036 @@ +import React from 'react'; +import { + act, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; +import { USERS_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import Chat from '../../../screens/UserPortal/Chat/Chat'; +import { + CREATE_CHAT, + MARK_CHAT_MESSAGES_AS_READ, + MESSAGE_SENT_TO_CHAT, +} from 'GraphQl/Mutations/OrganizationMutations'; +import { + CHATS_LIST, + CHAT_BY_ID, + GROUP_CHAT_LIST, + UNREAD_CHAT_LIST, +} from 'GraphQl/Queries/PlugInQueries'; +import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; +const { setItem } = useLocalStorage(); + +/** + * Unit tests for the Create Direct Chat Modal functionality in the User Portal + * + * These tests cover the following scenarios: + * 1. Opening and closing the create new direct chat modal, ensuring proper UI elements + * like dropdown, search input, and submit button are displayed and functional. + * 2. Creating a new direct chat, which includes testing the interaction with the add button, + * submitting the chat, and closing the modal. + * + * Tests involve interacting with the modal's UI elements, performing actions like + * opening the dropdown, searching for users, clicking on the submit button, and closing + * the modal. GraphQL mocks are used for testing chat-related queries and mutations, + * ensuring that the modal behaves as expected when interacting with the GraphQL API. + */ + +const UserConnectionListMock = [ + { + request: { + query: USERS_CONNECTION_LIST, + variables: { + firstName_contains: '', + lastName_contains: '', + }, + }, + result: { + data: { + users: [ + { + user: { + firstName: 'Deanne', + lastName: 'Marks', + image: null, + _id: '6589389d2caa9d8d69087487', + email: 'testuser8@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + organizationsBlockedBy: [], + joinedOrganizations: [ + { + _id: '6537904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Queens', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Coffee Street', + line2: 'Apartment 501', + postalCode: '11427', + sortingCode: 'ABC-133', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6637904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Staten Island', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 church Street', + line2: 'Apartment 499', + postalCode: '10301', + sortingCode: 'ABC-122', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6737904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Brooklyn', + countryCode: 'US', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Main Street', + line2: 'Apt 456', + postalCode: '10004', + sortingCode: 'ABC-789', + state: 'NY', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6437904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Bronx', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Random Street', + line2: 'Apartment 456', + postalCode: '10451', + sortingCode: 'ABC-123', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + ], + __typename: 'User', + }, + appUserProfile: { + _id: '64378abd85308f171cf2993d', + adminFor: [], + isSuperAdmin: false, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + __typename: 'AppUserProfile', + }, + __typename: 'UserData', + }, + ], + }, + }, + }, + { + request: { + query: USERS_CONNECTION_LIST, + variables: { + firstName_contains: 'Disha', + lastName_contains: '', + }, + }, + result: { + data: { + users: [ + { + user: { + firstName: 'Deanne', + lastName: 'Marks', + image: null, + _id: '6589389d2caa9d8d69087487', + email: 'testuser8@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + organizationsBlockedBy: [], + joinedOrganizations: [ + { + _id: '6537904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Queens', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Coffee Street', + line2: 'Apartment 501', + postalCode: '11427', + sortingCode: 'ABC-133', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6637904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Staten Island', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 church Street', + line2: 'Apartment 499', + postalCode: '10301', + sortingCode: 'ABC-122', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6737904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Brooklyn', + countryCode: 'US', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Main Street', + line2: 'Apt 456', + postalCode: '10004', + sortingCode: 'ABC-789', + state: 'NY', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6437904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Bronx', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Random Street', + line2: 'Apartment 456', + postalCode: '10451', + sortingCode: 'ABC-123', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + ], + __typename: 'User', + }, + appUserProfile: { + _id: '64378abd85308f171cf2993d', + adminFor: [], + isSuperAdmin: false, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + __typename: 'AppUserProfile', + }, + __typename: 'UserData', + }, + ], + }, + }, + }, +]; + +const MESSAGE_SENT_TO_CHAT_MOCK = [ + { + request: { + query: MESSAGE_SENT_TO_CHAT, + variables: { + userId: null, + }, + }, + result: { + data: { + messageSentToChat: { + _id: '668ec1f1364e03ac47a151', + chatMessageBelongsTo: { + _id: '1', + }, + createdAt: '2024-07-10T17:16:33.248Z', + messageContent: 'Test ', + type: 'STRING', + replyTo: null, + sender: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: '', + }, + updatedAt: '2024-07-10', + }, + }, + }, + }, + { + request: { + query: MESSAGE_SENT_TO_CHAT, + variables: { + userId: '2', + }, + }, + result: { + data: { + messageSentToChat: { + _id: '668ec1f1df364e03ac47a151', + chatMessageBelongsTo: { + _id: '1', + }, + createdAt: '2024-07-10T17:16:33.248Z', + messageContent: 'Test ', + replyTo: null, + type: 'STRING', + sender: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: '', + }, + updatedAt: '2024-07-10', + }, + }, + }, + }, + { + request: { + query: MESSAGE_SENT_TO_CHAT, + variables: { + userId: '1', + }, + }, + result: { + data: { + messageSentToChat: { + _id: '668ec1f13603ac4697a151', + chatMessageBelongsTo: { + _id: '1', + }, + createdAt: '2024-07-10T17:16:33.248Z', + messageContent: 'Test ', + replyTo: null, + type: 'STRING', + sender: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: '', + }, + updatedAt: '2024-07-10', + }, + }, + }, + }, +]; + +const CHAT_BY_ID_QUERY_MOCK = [ + { + request: { + query: CHAT_BY_ID, + variables: { + id: '1', + }, + }, + result: { + data: { + chatById: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: CHAT_BY_ID, + variables: { + id: '1', + }, + }, + result: { + data: { + chatById: { + _id: '1', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + createdAt: '2345678903456', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: CHAT_BY_ID, + variables: { + id: '', + }, + }, + result: { + data: { + chatById: { + _id: '1', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + createdAt: '2345678903456', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, +]; + +const UNREAD_CHAT_LIST_QUERY_MOCK = [ + { + request: { + query: UNREAD_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getUnreadChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, +]; + +const GROUP_CHAT_BY_USER_ID_QUERY_MOCK = [ + { + request: { + query: GROUP_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 0, + '2': 0, + }), + }, + }, + }, + }, +]; + +const CHATS_LIST_MOCK = [ + { + request: { + query: CHATS_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dd40fgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dd40fgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dd40fgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dd40fgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dd40fgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844ghjefc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: 'ujhgtrdtyuiop', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dhjmkdftyd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844ewsedrffc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dhjmkdftyd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844ewsedrffc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dhjmkdftyd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + media: null, + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844ewsedrffc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dhjmkdftyd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844ewsedrffc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dhjmkdftyd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844ewsedrffc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, +]; + +const GROUP_CHAT_BY_ID_QUERY_MOCK = [ + { + request: { + query: CHAT_BY_ID, + variables: { + id: '', + }, + }, + result: { + data: { + chatById: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, +]; + +const CREATE_CHAT_MUTATION_MOCK = [ + { + request: { + query: CREATE_CHAT, + variables: { + organizationId: undefined, + userIds: ['1', '6589389d2caa9d8d69087487'], + isGroup: false, + }, + }, + result: { + data: { + createChat: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + type: 'STRING', + media: null, + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + }), + }, + }, + }, + }, +]; + +const MARK_CHAT_MESSAGES_AS_READ_MOCK = [ + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: null, + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: null, + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: null, + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: null, + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: '1', + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: '1', + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, +]; +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing Create Direct Chat Modal [User Portal]', () => { + window.HTMLElement.prototype.scrollIntoView = vi.fn(); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // Deprecated + removeListener: vi.fn(), // Deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); + + it('Open and close create new direct chat modal', async () => { + const mock = [ + ...GROUP_CHAT_BY_ID_QUERY_MOCK, + ...MESSAGE_SENT_TO_CHAT_MOCK, + ...UserConnectionListMock, + ...CHATS_LIST_MOCK, + ...CHAT_BY_ID_QUERY_MOCK, + ...CREATE_CHAT_MUTATION_MOCK, + ...MARK_CHAT_MESSAGES_AS_READ_MOCK, + ...UNREAD_CHAT_LIST_QUERY_MOCK, + ...GROUP_CHAT_BY_USER_ID_QUERY_MOCK, + ]; + render( + + + + + + + + + , + ); + + await wait(); + + const dropdown = await screen.findByTestId('dropdown'); + expect(dropdown).toBeInTheDocument(); + fireEvent.click(dropdown); + const newDirectChatBtn = await screen.findByTestId('newDirectChat'); + expect(newDirectChatBtn).toBeInTheDocument(); + fireEvent.click(newDirectChatBtn); + + const submitBtn = await screen.findByTestId('submitBtn'); + expect(submitBtn).toBeInTheDocument(); + + const searchInput = (await screen.findByTestId( + 'searchUser', + )) as HTMLInputElement; + expect(searchInput).toBeInTheDocument(); + + fireEvent.change(searchInput, { target: { value: 'Disha' } }); + + expect(searchInput.value).toBe('Disha'); + + fireEvent.click(submitBtn); + + const closeButton = screen.getByRole('button', { name: /close/i }); + expect(closeButton).toBeInTheDocument(); + + fireEvent.click(closeButton); + }); + + it('create new direct chat', async () => { + setItem('userId', '1'); + const mock = [ + ...GROUP_CHAT_BY_ID_QUERY_MOCK, + ...MESSAGE_SENT_TO_CHAT_MOCK, + ...UserConnectionListMock, + ...CHATS_LIST_MOCK, + ...CHAT_BY_ID_QUERY_MOCK, + ...CREATE_CHAT_MUTATION_MOCK, + ...MARK_CHAT_MESSAGES_AS_READ_MOCK, + ...UNREAD_CHAT_LIST_QUERY_MOCK, + ...GROUP_CHAT_BY_USER_ID_QUERY_MOCK, + ]; + render( + + + + + + + + + , + ); + + await wait(); + + const dropdown = await screen.findByTestId('dropdown'); + expect(dropdown).toBeInTheDocument(); + fireEvent.click(dropdown); + const newDirectChatBtn = await screen.findByTestId('newDirectChat'); + expect(newDirectChatBtn).toBeInTheDocument(); + fireEvent.click(newDirectChatBtn); + + const addBtn = await screen.findAllByTestId('addBtn'); + waitFor(() => { + expect(addBtn[0]).toBeInTheDocument(); + }); + + fireEvent.click(addBtn[0]); + + const closeButton = screen.getByRole('button', { name: /close/i }); + expect(closeButton).toBeInTheDocument(); + + fireEvent.click(closeButton); + + await new Promise(process.nextTick); + + await wait(); + }); +}); diff --git a/src/components/UserPortal/CreateDirectChat/CreateDirectChat.test.tsx b/src/components/UserPortal/CreateDirectChat/CreateDirectChat.test.tsx deleted file mode 100644 index d8b3466207..0000000000 --- a/src/components/UserPortal/CreateDirectChat/CreateDirectChat.test.tsx +++ /dev/null @@ -1,1496 +0,0 @@ -import React from 'react'; -import { - act, - fireEvent, - render, - screen, - waitFor, -} from '@testing-library/react'; -import { MockedProvider } from '@apollo/react-testing'; -import { I18nextProvider } from 'react-i18next'; -import { USERS_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; -import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; -import Chat from '../../../screens/UserPortal/Chat/Chat'; -import { - CREATE_CHAT, - MESSAGE_SENT_TO_CHAT, -} from 'GraphQl/Mutations/OrganizationMutations'; -import { CHATS_LIST, CHAT_BY_ID } from 'GraphQl/Queries/PlugInQueries'; -import useLocalStorage from 'utils/useLocalstorage'; - -const { setItem } = useLocalStorage(); - -const UserConnectionListMock = [ - { - request: { - query: USERS_CONNECTION_LIST, - variables: { - firstName_contains: '', - lastName_contains: '', - }, - }, - result: { - data: { - users: [ - { - user: { - firstName: 'Deanne', - lastName: 'Marks', - image: null, - _id: '6589389d2caa9d8d69087487', - email: 'testuser8@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - organizationsBlockedBy: [], - joinedOrganizations: [ - { - _id: '6537904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Queens', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 Coffee Street', - line2: 'Apartment 501', - postalCode: '11427', - sortingCode: 'ABC-133', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6637904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Staten Island', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 church Street', - line2: 'Apartment 499', - postalCode: '10301', - sortingCode: 'ABC-122', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6737904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Brooklyn', - countryCode: 'US', - dependentLocality: 'Sample Dependent Locality', - line1: '123 Main Street', - line2: 'Apt 456', - postalCode: '10004', - sortingCode: 'ABC-789', - state: 'NY', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6437904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Bronx', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 Random Street', - line2: 'Apartment 456', - postalCode: '10451', - sortingCode: 'ABC-123', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - ], - __typename: 'User', - }, - appUserProfile: { - _id: '64378abd85308f171cf2993d', - adminFor: [], - isSuperAdmin: false, - createdOrganizations: [], - createdEvents: [], - eventAdmin: [], - __typename: 'AppUserProfile', - }, - __typename: 'UserData', - }, - ], - }, - }, - }, - { - request: { - query: USERS_CONNECTION_LIST, - variables: { - firstName_contains: 'Disha', - lastName_contains: '', - }, - }, - result: { - data: { - users: [ - { - user: { - firstName: 'Deanne', - lastName: 'Marks', - image: null, - _id: '6589389d2caa9d8d69087487', - email: 'testuser8@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - organizationsBlockedBy: [], - joinedOrganizations: [ - { - _id: '6537904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Queens', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 Coffee Street', - line2: 'Apartment 501', - postalCode: '11427', - sortingCode: 'ABC-133', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6637904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Staten Island', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 church Street', - line2: 'Apartment 499', - postalCode: '10301', - sortingCode: 'ABC-122', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6737904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Brooklyn', - countryCode: 'US', - dependentLocality: 'Sample Dependent Locality', - line1: '123 Main Street', - line2: 'Apt 456', - postalCode: '10004', - sortingCode: 'ABC-789', - state: 'NY', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6437904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Bronx', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 Random Street', - line2: 'Apartment 456', - postalCode: '10451', - sortingCode: 'ABC-123', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - ], - __typename: 'User', - }, - appUserProfile: { - _id: '64378abd85308f171cf2993d', - adminFor: [], - isSuperAdmin: false, - createdOrganizations: [], - createdEvents: [], - eventAdmin: [], - __typename: 'AppUserProfile', - }, - __typename: 'UserData', - }, - ], - }, - }, - }, -]; - -const MESSAGE_SENT_TO_CHAT_MOCK = [ - { - request: { - query: MESSAGE_SENT_TO_CHAT, - variables: { - userId: null, - }, - }, - result: { - data: { - messageSentToChat: { - _id: '668ec1f1364e03ac47a151', - createdAt: '2024-07-10T17:16:33.248Z', - messageContent: 'Test ', - type: 'STRING', - replyTo: null, - sender: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: '', - }, - updatedAt: '2024-07-10', - }, - }, - }, - }, - { - request: { - query: MESSAGE_SENT_TO_CHAT, - variables: { - userId: '2', - }, - }, - result: { - data: { - messageSentToChat: { - _id: '668ec1f1df364e03ac47a151', - createdAt: '2024-07-10T17:16:33.248Z', - messageContent: 'Test ', - replyTo: null, - type: 'STRING', - sender: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: '', - }, - updatedAt: '2024-07-10', - }, - }, - }, - }, - { - request: { - query: MESSAGE_SENT_TO_CHAT, - variables: { - userId: '1', - }, - }, - result: { - data: { - messageSentToChat: { - _id: '668ec1f13603ac4697a151', - createdAt: '2024-07-10T17:16:33.248Z', - messageContent: 'Test ', - replyTo: null, - type: 'STRING', - sender: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: '', - }, - updatedAt: '2024-07-10', - }, - }, - }, - }, -]; - -const CHAT_BY_ID_QUERY_MOCK = [ - { - request: { - query: CHAT_BY_ID, - variables: { - id: '1', - }, - }, - result: { - data: { - chatById: { - _id: '1', - createdAt: '2345678903456', - isGroup: false, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: null, - name: '', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - ], - }, - }, - }, - }, - { - request: { - query: CHAT_BY_ID, - variables: { - id: '1', - }, - }, - result: { - data: { - chatById: { - _id: '1', - isGroup: false, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: null, - name: '', - createdAt: '2345678903456', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - ], - }, - }, - }, - }, - { - request: { - query: CHAT_BY_ID, - variables: { - id: '', - }, - }, - result: { - data: { - chatById: { - _id: '1', - isGroup: false, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: null, - name: '', - createdAt: '2345678903456', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - ], - }, - }, - }, - }, -]; - -const CHATS_LIST_MOCK = [ - { - request: { - query: CHATS_LIST, - variables: { - id: null, - }, - }, - result: { - data: { - chatsByUserId: [ - { - _id: '65844efc814dd40fgh03db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - { - _id: '65844efc814ddgh4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: CHATS_LIST, - variables: { - id: '', - }, - }, - result: { - data: { - chatsByUserId: [ - { - _id: '65844ghjefc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - { - _id: 'ujhgtrdtyuiop', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: CHATS_LIST, - variables: { - id: '1', - }, - }, - result: { - data: { - chatsByUserId: [ - { - _id: '65844efc814dhjmkdftyd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - { - _id: '65844ewsedrffc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: CHATS_LIST, - variables: { - id: '1', - }, - }, - result: { - data: { - chatsByUserId: [ - { - _id: '65844efc814dhjmkdftyd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - { - _id: '65844ewsedrffc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - ], - }, - }, - }, -]; - -const GROUP_CHAT_BY_ID_QUERY_MOCK = [ - { - request: { - query: CHAT_BY_ID, - variables: { - id: '', - }, - }, - result: { - data: { - chatById: { - _id: '65844efc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - }, - }, - }, -]; - -const CREATE_CHAT_MUTATION_MOCK = [ - { - request: { - query: CREATE_CHAT, - variables: { - organizationId: undefined, - userIds: ['1', '6589389d2caa9d8d69087487'], - isGroup: false, - }, - }, - result: { - data: { - createChat: { - _id: '1', - createdAt: '2345678903456', - isGroup: false, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: null, - name: '', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - ], - }, - }, - }, - }, -]; - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -describe('Testing Create Direct Chat Modal [User Portal]', () => { - window.HTMLElement.prototype.scrollIntoView = jest.fn(); - - Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); - - test('Open and close create new direct chat modal', async () => { - const mock = [ - ...GROUP_CHAT_BY_ID_QUERY_MOCK, - ...MESSAGE_SENT_TO_CHAT_MOCK, - ...UserConnectionListMock, - ...CHATS_LIST_MOCK, - ...CHAT_BY_ID_QUERY_MOCK, - ...CREATE_CHAT_MUTATION_MOCK, - ]; - render( - - - - - - - - - , - ); - - await wait(); - - const dropdown = await screen.findByTestId('dropdown'); - expect(dropdown).toBeInTheDocument(); - fireEvent.click(dropdown); - const newDirectChatBtn = await screen.findByTestId('newDirectChat'); - expect(newDirectChatBtn).toBeInTheDocument(); - fireEvent.click(newDirectChatBtn); - - const submitBtn = await screen.findByTestId('submitBtn'); - expect(submitBtn).toBeInTheDocument(); - - const searchInput = (await screen.findByTestId( - 'searchUser', - )) as HTMLInputElement; - expect(searchInput).toBeInTheDocument(); - - fireEvent.change(searchInput, { target: { value: 'Disha' } }); - - expect(searchInput.value).toBe('Disha'); - - fireEvent.click(submitBtn); - - const closeButton = screen.getByRole('button', { name: /close/i }); - expect(closeButton).toBeInTheDocument(); - - fireEvent.click(closeButton); - }); - - test('create new direct chat', async () => { - setItem('userId', '1'); - const mock = [ - ...GROUP_CHAT_BY_ID_QUERY_MOCK, - ...MESSAGE_SENT_TO_CHAT_MOCK, - ...UserConnectionListMock, - ...CHATS_LIST_MOCK, - ...CHAT_BY_ID_QUERY_MOCK, - ...CREATE_CHAT_MUTATION_MOCK, - ]; - render( - - - - - - - - - , - ); - - await wait(); - - const dropdown = await screen.findByTestId('dropdown'); - expect(dropdown).toBeInTheDocument(); - fireEvent.click(dropdown); - const newDirectChatBtn = await screen.findByTestId('newDirectChat'); - expect(newDirectChatBtn).toBeInTheDocument(); - fireEvent.click(newDirectChatBtn); - - const addBtn = await screen.findAllByTestId('addBtn'); - waitFor(() => { - expect(addBtn[0]).toBeInTheDocument(); - }); - - fireEvent.click(addBtn[0]); - - const closeButton = screen.getByRole('button', { name: /close/i }); - expect(closeButton).toBeInTheDocument(); - - fireEvent.click(closeButton); - - await new Promise(process.nextTick); - - await wait(); - }); -}); diff --git a/src/components/UserPortal/CreateDirectChat/CreateDirectChat.tsx b/src/components/UserPortal/CreateDirectChat/CreateDirectChat.tsx index 24c853fcdd..4b1c2cda02 100644 --- a/src/components/UserPortal/CreateDirectChat/CreateDirectChat.tsx +++ b/src/components/UserPortal/CreateDirectChat/CreateDirectChat.tsx @@ -1,7 +1,6 @@ import { Paper, TableBody } from '@mui/material'; import React, { useState } from 'react'; import { Button, Form, Modal } from 'react-bootstrap'; -import styles from './CreateDirectChat.module.css'; import type { ApolloQueryResult } from '@apollo/client'; import { useMutation, useQuery } from '@apollo/client'; import useLocalStorage from 'utils/useLocalstorage'; @@ -17,6 +16,8 @@ import { USERS_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; import Loader from 'components/Loader/Loader'; import { Search } from '@mui/icons-material'; import { useParams } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import styles from '../../../style/app.module.css'; interface InterfaceCreateDirectChatProps { toggleCreateDirectChatModal: () => void; @@ -27,7 +28,7 @@ interface InterfaceCreateDirectChatProps { id: string; }> | undefined, - ) => Promise>; + ) => Promise>; } /** @@ -61,6 +62,9 @@ export default function createDirectChatModal({ createDirectChatModalisOpen, chatsListRefetch, }: InterfaceCreateDirectChatProps): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'userChat', + }); const { orgId: organizationId } = useParams(); const userId: string | null = getItem('userId'); @@ -125,7 +129,7 @@ export default function createDirectChatModal({ ) : ( <> -
+
- + @@ -187,7 +194,7 @@ export default function createDirectChatModal({ }} data-testid="addBtn" > - Add + {t('add')} diff --git a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.module.css b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.module.css index 3795e402fa..77fcfaf38f 100644 --- a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.module.css +++ b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.module.css @@ -7,3 +7,32 @@ .modalContent { width: 530px; } + +.groupInfo { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.groupImage { + margin-bottom: 10px; +} + +.editImgBtn { + padding: 2px 6px 6px 8px; + border-radius: 100%; + background-color: white; + border: 1px solid #959595; + color: #959595; + outline: none; + position: relative; + top: -40px; + left: 40px; +} + +.chatImage { + height: 120px; + border-radius: 100%; + width: 120px; +} diff --git a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.spec.tsx b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.spec.tsx new file mode 100644 index 0000000000..440273cbc4 --- /dev/null +++ b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.spec.tsx @@ -0,0 +1,5801 @@ +import React from 'react'; +import { + act, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { + USERS_CONNECTION_LIST, + USER_JOINED_ORGANIZATIONS, +} from 'GraphQl/Queries/Queries'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import Chat from '../../../screens/UserPortal/Chat/Chat'; +import { + CREATE_CHAT, + MARK_CHAT_MESSAGES_AS_READ, + MESSAGE_SENT_TO_CHAT, +} from 'GraphQl/Mutations/OrganizationMutations'; +import { + CHATS_LIST, + CHAT_BY_ID, + GROUP_CHAT_LIST, + UNREAD_CHAT_LIST, +} from 'GraphQl/Queries/PlugInQueries'; +import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; + +/** + * Unit tests for the Create Group Chat Modal functionality in the User Portal + * + * These tests cover the following scenarios: + * 1. Opening and closing the create new group chat modal, ensuring proper UI elements + * like the dropdown, new group chat button, and close button are displayed and functional. + * 2. Creating a new group chat by interacting with the group name input field, organization + * selection, and submission process. It also ensures that the create button is properly + * triggered after filling out the required fields. + * 3. Adding and removing users from the group chat, testing the interactions with the add + * and remove buttons, and verifying the submit button and search functionality for user selection. + * + * GraphQL mocks are used to simulate chat-related queries and mutations. The tests ensure that + * the modal behaves correctly in various user interaction scenarios, including handling of form + * fields, user management, and modal navigation. + */ + +const { setItem } = useLocalStorage(); + +const USER_JOINED_ORG_MOCK = [ + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: '1', + }, + }, + result: { + data: { + users: [ + { + user: { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'Test Org 1', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'Test Org 1', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'Test Org 1', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'Test Org 1', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + ], + }, + }, + ], + }, + }, + }, + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: '1', + }, + }, + result: { + data: { + users: [ + { + user: { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'Test org', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + { + __typename: 'Organization', + _id: 'qsxhgjhbmnbkhlk,njgjfhgv', + name: 'Any Organization', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + ], + }, + }, + ], + }, + }, + }, + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: '1', + }, + }, + result: { + data: { + users: [ + { + user: { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8fhgjhnm07af2', + name: 'Test org', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + { + __typename: 'Organization', + _id: '6401ff65ce8egfhbn8406b8f07af2', + name: 'Any Organization', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + ], + }, + }, + ], + }, + }, + }, + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: null, + }, + }, + result: { + data: { + users: [ + { + user: { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65fghce8e8406b8f07af2', + name: 'Test org', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8jygjgf07af2', + name: 'Any Organization', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + ], + }, + }, + ], + }, + }, + }, + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: null, + }, + }, + result: { + data: { + users: [ + { + user: { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65cehgh8e8406b8f07af2', + name: 'Test org', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'Any Organization', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + ], + }, + }, + ], + }, + }, + }, + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: null, + }, + }, + result: { + data: { + users: [ + { + user: { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406nbmnmb8f07af2', + name: 'Test org', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8fnnmm07af2', + name: 'Any Organization', + image: '', + description: 'New Desc', + address: { + city: 'abc', + countryCode: '123', + postalCode: '456', + state: 'def', + dependentLocality: 'ghi', + line1: 'asdfg', + line2: 'dfghj', + sortingCode: '4567', + }, + createdAt: '1234567890', + userRegistrationRequired: true, + creator: { + __typename: 'User', + firstName: 'John', + lastName: 'Doe', + }, + members: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + admins: [ + { + _id: '45gj5678jk45678fvgbhnr4rtgh', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: '45ydeg2yet721rtgdu32ry', + }, + }, + ], + }, + ], + }, + }, + ], + }, + }, + }, +]; + +const UserConnectionListMock = [ + { + request: { + query: USERS_CONNECTION_LIST, + variables: { + firstName_contains: '', + lastName_contains: '', + }, + }, + result: { + data: { + users: [ + { + user: { + firstName: 'Deanne', + lastName: 'Marks', + image: null, + _id: '6589389d2caa9d8d69087487', + email: 'testuser8@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + organizationsBlockedBy: [], + joinedOrganizations: [ + { + _id: '6537904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Queens', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Coffee Street', + line2: 'Apartment 501', + postalCode: '11427', + sortingCode: 'ABC-133', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6637904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Staten Island', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 church Street', + line2: 'Apartment 499', + postalCode: '10301', + sortingCode: 'ABC-122', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6737904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Brooklyn', + countryCode: 'US', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Main Street', + line2: 'Apt 456', + postalCode: '10004', + sortingCode: 'ABC-789', + state: 'NY', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6437904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Bronx', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Random Street', + line2: 'Apartment 456', + postalCode: '10451', + sortingCode: 'ABC-123', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + ], + __typename: 'User', + }, + appUserProfile: { + _id: '64378abd85308f171cf2993d', + adminFor: [], + isSuperAdmin: false, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + __typename: 'AppUserProfile', + }, + __typename: 'UserData', + }, + ], + }, + }, + }, + { + request: { + query: USERS_CONNECTION_LIST, + variables: { + firstName_contains: '', + lastName_contains: '', + }, + }, + result: { + data: { + users: { + user: [ + { + firstName: 'Disha', + lastName: 'Talreja', + image: 'img', + _id: '1', + email: 'disha@email.com', + createdAt: '', + appUserProfile: { + _id: '12', + isSuperAdmin: 'false', + createdOrganizations: { + _id: '345678', + }, + createdEvents: { + _id: '34567890', + }, + }, + organizationsBlockedBy: [], + joinedOrganizations: [], + }, + { + firstName: 'Disha', + lastName: 'Talreja', + image: 'img', + _id: '1', + email: 'disha@email.com', + createdAt: '', + appUserProfile: { + _id: '12', + isSuperAdmin: 'false', + createdOrganizations: { + _id: '345678', + }, + createdEvents: { + _id: '34567890', + }, + }, + organizationsBlockedBy: [], + joinedOrganizations: [], + }, + { + firstName: 'Disha', + lastName: 'Talreja', + image: 'img', + _id: '1', + email: 'disha@email.com', + createdAt: '', + appUserProfile: { + _id: '12', + isSuperAdmin: 'false', + createdOrganizations: { + _id: '345678', + }, + createdEvents: { + _id: '34567890', + }, + }, + organizationsBlockedBy: [], + joinedOrganizations: [], + }, + ], + }, + }, + }, + }, + { + request: { + query: USERS_CONNECTION_LIST, + variables: { + firstName_contains: 'Disha', + lastName_contains: '', + }, + }, + result: { + data: { + users: [ + { + user: { + firstName: 'Deanne', + lastName: 'Marks', + image: null, + _id: '6589389d2caa9d8d69087487', + email: 'testuser8@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + organizationsBlockedBy: [], + joinedOrganizations: [ + { + _id: '6537904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Queens', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Coffee Street', + line2: 'Apartment 501', + postalCode: '11427', + sortingCode: 'ABC-133', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6637904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Staten Island', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 church Street', + line2: 'Apartment 499', + postalCode: '10301', + sortingCode: 'ABC-122', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6737904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Brooklyn', + countryCode: 'US', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Main Street', + line2: 'Apt 456', + postalCode: '10004', + sortingCode: 'ABC-789', + state: 'NY', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + { + _id: '6437904485008f171cf29924', + name: 'Unity Foundation', + image: null, + address: { + city: 'Bronx', + countryCode: 'US', + dependentLocality: 'Some Dependent Locality', + line1: '123 Random Street', + line2: 'Apartment 456', + postalCode: '10451', + sortingCode: 'ABC-123', + state: 'NYC', + __typename: 'Address', + }, + createdAt: '2023-04-13T05:16:52.827Z', + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + __typename: 'Organization', + }, + ], + __typename: 'User', + }, + appUserProfile: { + _id: '64378abd85308f171cf2993d', + adminFor: [], + isSuperAdmin: false, + createdOrganizations: [], + createdEvents: [], + eventAdmin: [], + __typename: 'AppUserProfile', + }, + __typename: 'UserData', + }, + ], + }, + }, + }, +]; + +const MESSAGE_SENT_TO_CHAT_MOCK = [ + { + request: { + query: MESSAGE_SENT_TO_CHAT, + variables: { + userId: null, + }, + }, + result: { + data: { + messageSentToChat: { + _id: '668ec1f1364e03ac47a151', + chatMessageBelongsTo: { + _id: '1', + }, + createdAt: '2024-07-10T17:16:33.248Z', + messageContent: 'Test ', + type: 'STRING', + replyTo: null, + sender: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: '', + }, + updatedAt: '2024-07-10', + }, + }, + }, + }, + { + request: { + query: MESSAGE_SENT_TO_CHAT, + variables: { + userId: '2', + }, + }, + result: { + data: { + messageSentToChat: { + _id: '668ec1f1df364e03ac47a151', + chatMessageBelongsTo: { + _id: '1', + }, + createdAt: '2024-07-10T17:16:33.248Z', + messageContent: 'Test ', + replyTo: null, + type: 'STRING', + sender: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: '', + }, + updatedAt: '2024-07-10', + }, + }, + }, + }, + { + request: { + query: MESSAGE_SENT_TO_CHAT, + variables: { + userId: '1', + }, + }, + result: { + data: { + messageSentToChat: { + _id: '668ec1f13603ac4697a151', + chatMessageBelongsTo: { + _id: '1', + }, + createdAt: '2024-07-10T17:16:33.248Z', + messageContent: 'Test ', + replyTo: null, + type: 'STRING', + sender: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: '', + }, + updatedAt: '2024-07-10', + }, + }, + }, + }, +]; + +const CHAT_BY_ID_QUERY_MOCK = [ + { + request: { + query: CHAT_BY_ID, + variables: { + id: '1', + }, + }, + result: { + data: { + chatById: { + _id: '1', + createdAt: '2345678903456', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + }), + }, + }, + }, + }, + { + request: { + query: CHAT_BY_ID, + variables: { + id: '1', + }, + }, + result: { + data: { + chatById: { + _id: '1', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + createdAt: '2345678903456', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + }), + }, + }, + }, + }, + { + request: { + query: CHAT_BY_ID, + variables: { + id: '', + }, + }, + result: { + data: { + chatById: { + _id: '1', + isGroup: false, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: null, + name: '', + createdAt: '2345678903456', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + }), + }, + }, + }, + }, +]; + +const UNREAD_CHAT_BY_USER_ID_QUERY_MOCK = [ + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: UNREAD_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getUnreadChatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, +]; + +const CHATS_LIST_MOCK = [ + { + request: { + query: CHATS_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dd40hgjfgh03db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844ghjefc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: 'fcfgcgchnbjhgfrftghj', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dd40fgh03db8gjhbhn11c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844efc814ddgh4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844ghjefc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: 'ujhgtrdtyuiop', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, + { + request: { + query: CHATS_LIST, + variables: { + id: '1', + }, + }, + result: { + data: { + chatsByUserId: [ + { + _id: '65844efc814dhjmkdftyd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + { + _id: '65844ewsedrffc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + ], + }, + }, + }, +]; + +const GROUP_CHAT_LIST_QUERY_MOCK = [ + { + request: { + query: GROUP_CHAT_LIST, + variables: { + id: '', + }, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: {}, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: GROUP_CHAT_LIST, + variables: { + id: null, + }, + }, + result: { + data: { + getGroupChatsByUserId: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, +]; + +const GROUP_CHAT_BY_ID_QUERY_MOCK = [ + { + request: { + query: CHAT_BY_ID, + variables: { + id: '', + }, + }, + result: { + data: { + chatById: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + ], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, +]; + +const CREATE_CHAT_MUTATION = [ + { + request: { + query: CREATE_CHAT, + variables: { + organizationId: '6401ff65ce8e8406b8jygjgf07af2', + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: CREATE_CHAT, + variables: { + organizationId: '6401ff65ce8e8406b8jygjgf07af2', + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: CREATE_CHAT, + variables: { + organizationId: '6401ff65ce8e8406b8jygjgf07af2', + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: CREATE_CHAT, + variables: { + organizationId: '6401ff65ce8e8406b8jygjgf07af2', + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: CREATE_CHAT, + variables: { + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: CREATE_CHAT, + variables: { + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: CREATE_CHAT, + variables: { + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: CREATE_CHAT, + variables: { + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + type: 'STRING', + media: null, + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, + { + request: { + query: CREATE_CHAT, + variables: { + organizationId: '', + userIds: [null], + name: 'Test Group', + isGroup: true, + image: null, + }, + }, + result: { + data: { + createChat: { + _id: '65844efc814dd4003db811c4', + isGroup: true, + image: null, + creator: { + _id: '64378abd85008f171cf2990d', + firstName: 'Wilt', + lastName: 'Shepherd', + image: null, + email: 'testsuperadmin@example.com', + createdAt: '2023-04-13T04:53:17.742Z', + __typename: 'User', + }, + organization: { + _id: 'pw3ertyuiophgfre45678', + name: 'rtyu', + }, + createdAt: '2345678903456', + name: 'Test Group Chat', + messages: [ + { + _id: '345678', + createdAt: '345678908765', + messageContent: 'Hello', + replyTo: null, + media: null, + type: 'STRING', + sender: { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + }, + ], + users: [ + { + _id: '1', + firstName: 'Disha', + lastName: 'Talreja', + email: 'disha@example.com', + image: '', + }, + { + _id: '2', + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + image: '', + }, + { + _id: '3', + firstName: 'Test', + lastName: 'User1', + email: 'test1@example.com', + image: '', + }, + { + _id: '4', + firstName: 'Test', + lastName: 'User2', + email: 'test2@example.com', + image: '', + }, + { + _id: '5', + firstName: 'Test', + lastName: 'User4', + email: 'test4@example.com', + image: '', + }, + ], + admins: [], + unseenMessagesByUsers: JSON.stringify({ + '1': 1, + '2': 1, + '3': 1, + '4': 1, + '5': 1, + }), + }, + }, + }, + }, +]; + +const MARK_CHAT_MESSAGES_AS_READ_MOCK = [ + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: null, + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: null, + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: '1', + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: '1', + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: MARK_CHAT_MESSAGES_AS_READ, + variables: { + userId: '1', + chatId: '', + }, + }, + result: { + data: { + markChatMessagesAsRead: { + _id: '1', + }, + }, + }, + }, +]; + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing Create Group Chat Modal [User Portal]', () => { + window.HTMLElement.prototype.scrollIntoView = vi.fn(); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // Deprecated + removeListener: vi.fn(), // Deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); + + it('open and close create new direct chat modal', async () => { + const mock = [ + ...USER_JOINED_ORG_MOCK, + ...GROUP_CHAT_BY_ID_QUERY_MOCK, + ...MESSAGE_SENT_TO_CHAT_MOCK, + ...CHAT_BY_ID_QUERY_MOCK, + ...CHATS_LIST_MOCK, + ...UserConnectionListMock, + ...CREATE_CHAT_MUTATION, + ...CHATS_LIST_MOCK, + ...MARK_CHAT_MESSAGES_AS_READ_MOCK, + ...UNREAD_CHAT_BY_USER_ID_QUERY_MOCK, + ...GROUP_CHAT_LIST_QUERY_MOCK, + ]; + render( + + + + + + + + + , + ); + + await wait(); + + const dropdown = await screen.findByTestId('dropdown'); + expect(dropdown).toBeInTheDocument(); + fireEvent.click(dropdown); + const newGroupChatBtn = await screen.findByTestId('newDirectChat'); + expect(newGroupChatBtn).toBeInTheDocument(); + fireEvent.click(newGroupChatBtn); + + const closeButton = screen.getByRole('button', { name: /close/i }); + expect(closeButton).toBeInTheDocument(); + + fireEvent.click(closeButton); + }); + + it('create new group chat', async () => { + const mock = [ + ...USER_JOINED_ORG_MOCK, + ...GROUP_CHAT_BY_ID_QUERY_MOCK, + ...MESSAGE_SENT_TO_CHAT_MOCK, + ...CHAT_BY_ID_QUERY_MOCK, + ...CHATS_LIST_MOCK, + ...UserConnectionListMock, + ...CREATE_CHAT_MUTATION, + ...CHATS_LIST_MOCK, + ...MARK_CHAT_MESSAGES_AS_READ_MOCK, + ...UNREAD_CHAT_BY_USER_ID_QUERY_MOCK, + ...GROUP_CHAT_LIST_QUERY_MOCK, + ]; + render( + + + + + + + + + , + ); + + await wait(); + + const dropdown = await screen.findByTestId('dropdown'); + expect(dropdown).toBeInTheDocument(); + fireEvent.click(dropdown); + + const newGroupChatBtn = await screen.findByTestId('newGroupChat'); + expect(newGroupChatBtn).toBeInTheDocument(); + + fireEvent.click(newGroupChatBtn); + + await waitFor(async () => { + expect( + await screen.findByTestId('createGroupChatModal'), + ).toBeInTheDocument(); + }); + + const groupTitleInput = (await screen.findByTestId( + 'groupTitleInput', + )) as unknown as HTMLInputElement; + + expect(groupTitleInput).toBeInTheDocument(); + + fireEvent.change(groupTitleInput, { target: { value: 'Test Group' } }); + await waitFor(() => { + expect(groupTitleInput.value).toBe('Test Group'); + }); + + const nextBtn = await screen.findByTestId('nextBtn'); + + act(() => { + fireEvent.click(nextBtn); + }); + + const createBtn = await screen.findByTestId('createBtn'); + await waitFor(async () => { + expect(createBtn).toBeInTheDocument(); + }); + + await act(async () => { + fireEvent.click(await screen.findByTestId('createBtn')); + }); + + await waitFor(() => { + expect(createBtn).not.toBeInTheDocument(); + }); + }, 3000); + + it('add and remove user', async () => { + setItem('userId', '1'); + const mock = [ + ...USER_JOINED_ORG_MOCK, + ...GROUP_CHAT_BY_ID_QUERY_MOCK, + ...MESSAGE_SENT_TO_CHAT_MOCK, + ...CHAT_BY_ID_QUERY_MOCK, + ...CHATS_LIST_MOCK, + ...UserConnectionListMock, + ...CREATE_CHAT_MUTATION, + ...CHATS_LIST_MOCK, + ...MARK_CHAT_MESSAGES_AS_READ_MOCK, + ...UNREAD_CHAT_BY_USER_ID_QUERY_MOCK, + ...GROUP_CHAT_LIST_QUERY_MOCK, + ]; + render( + + + + + + + + + , + ); + + await wait(); + + const dropdown = await screen.findByTestId('dropdown'); + expect(dropdown).toBeInTheDocument(); + fireEvent.click(dropdown); + const newGroupChatBtn = await screen.findByTestId('newGroupChat'); + expect(newGroupChatBtn).toBeInTheDocument(); + fireEvent.click(newGroupChatBtn); + + await waitFor(async () => { + expect( + await screen.findByTestId('createGroupChatModal'), + ).toBeInTheDocument(); + }); + + const nextBtn = await screen.findByTestId('nextBtn'); + + act(() => { + fireEvent.click(nextBtn); + }); + + await waitFor(async () => { + const addBtn = await screen.findAllByTestId('addBtn'); + expect(addBtn[0]).toBeInTheDocument(); + }); + + const addBtn = await screen.findAllByTestId('addBtn'); + + fireEvent.click(addBtn[0]); + + const removeBtn = await screen.findAllByText('Remove'); + await waitFor(async () => { + expect(removeBtn[0]).toBeInTheDocument(); + }); + fireEvent.click(removeBtn[0]); + + await waitFor(() => { + expect(addBtn[0]).toBeInTheDocument(); + }); + + const submitBtn = await screen.findByTestId('submitBtn'); + + expect(submitBtn).toBeInTheDocument(); + + const searchInput = (await screen.findByTestId( + 'searchUser', + )) as HTMLInputElement; + expect(searchInput).toBeInTheDocument(); + + fireEvent.change(searchInput, { target: { value: 'Disha' } }); + + expect(searchInput.value).toBe('Disha'); + + fireEvent.click(submitBtn); + + const closeButton = screen.getAllByRole('button', { name: /close/i }); + expect(closeButton[0]).toBeInTheDocument(); + + fireEvent.click(closeButton[0]); + + await wait(500); + }); +}); diff --git a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.test.tsx b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.test.tsx deleted file mode 100644 index 055985d5fb..0000000000 --- a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.test.tsx +++ /dev/null @@ -1,2455 +0,0 @@ -import React from 'react'; -import { - act, - fireEvent, - render, - screen, - waitFor, - within, -} from '@testing-library/react'; -import { MockedProvider } from '@apollo/react-testing'; -import { I18nextProvider } from 'react-i18next'; - -import { - USERS_CONNECTION_LIST, - USER_JOINED_ORGANIZATIONS, -} from 'GraphQl/Queries/Queries'; -import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; -import Chat from '../../../screens/UserPortal/Chat/Chat'; -import { - CREATE_CHAT, - MESSAGE_SENT_TO_CHAT, -} from 'GraphQl/Mutations/OrganizationMutations'; -import { CHATS_LIST, CHAT_BY_ID } from 'GraphQl/Queries/PlugInQueries'; -import useLocalStorage from 'utils/useLocalstorage'; -import userEvent from '@testing-library/user-event'; - -const { setItem } = useLocalStorage(); - -const USER_JOINED_ORG_MOCK = [ - { - request: { - query: USER_JOINED_ORGANIZATIONS, - variables: { - id: '1', - }, - }, - result: { - data: { - users: [ - { - user: { - joinedOrganizations: [ - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8f07af2', - name: 'Test Org 1', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8f07af2', - name: 'Test Org 1', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8f07af2', - name: 'Test Org 1', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8f07af2', - name: 'Test Org 1', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - ], - }, - }, - ], - }, - }, - }, - { - request: { - query: USER_JOINED_ORGANIZATIONS, - variables: { - id: '1', - }, - }, - result: { - data: { - users: [ - { - user: { - joinedOrganizations: [ - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8f07af2', - name: 'Test org', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - { - __typename: 'Organization', - _id: 'qsxhgjhbmnbkhlk,njgjfhgv', - name: 'Any Organization', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - ], - }, - }, - ], - }, - }, - }, - { - request: { - query: USER_JOINED_ORGANIZATIONS, - variables: { - id: '1', - }, - }, - result: { - data: { - users: [ - { - user: { - joinedOrganizations: [ - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8fhgjhnm07af2', - name: 'Test org', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - { - __typename: 'Organization', - _id: '6401ff65ce8egfhbn8406b8f07af2', - name: 'Any Organization', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - ], - }, - }, - ], - }, - }, - }, - { - request: { - query: USER_JOINED_ORGANIZATIONS, - variables: { - id: null, - }, - }, - result: { - data: { - users: [ - { - user: { - joinedOrganizations: [ - { - __typename: 'Organization', - _id: '6401ff65fghce8e8406b8f07af2', - name: 'Test org', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8jygjgf07af2', - name: 'Any Organization', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - ], - }, - }, - ], - }, - }, - }, - { - request: { - query: USER_JOINED_ORGANIZATIONS, - variables: { - id: null, - }, - }, - result: { - data: { - users: [ - { - user: { - joinedOrganizations: [ - { - __typename: 'Organization', - _id: '6401ff65cehgh8e8406b8f07af2', - name: 'Test org', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8f07af2', - name: 'Any Organization', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - ], - }, - }, - ], - }, - }, - }, - { - request: { - query: USER_JOINED_ORGANIZATIONS, - variables: { - id: null, - }, - }, - result: { - data: { - users: [ - { - user: { - joinedOrganizations: [ - { - __typename: 'Organization', - _id: '6401ff65ce8e8406nbmnmb8f07af2', - name: 'Test org', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - { - __typename: 'Organization', - _id: '6401ff65ce8e8406b8fnnmm07af2', - name: 'Any Organization', - image: '', - description: 'New Desc', - address: { - city: 'abc', - countryCode: '123', - postalCode: '456', - state: 'def', - dependentLocality: 'ghi', - line1: 'asdfg', - line2: 'dfghj', - sortingCode: '4567', - }, - createdAt: '1234567890', - userRegistrationRequired: true, - creator: { - __typename: 'User', - firstName: 'John', - lastName: 'Doe', - }, - members: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - admins: [ - { - _id: '45gj5678jk45678fvgbhnr4rtgh', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - membershipRequests: [ - { - _id: '56gheqyr7deyfuiwfewifruy8', - user: { - _id: '45ydeg2yet721rtgdu32ry', - }, - }, - ], - }, - ], - }, - }, - ], - }, - }, - }, -]; - -const UserConnectionListMock = [ - { - request: { - query: USERS_CONNECTION_LIST, - variables: { - firstName_contains: '', - lastName_contains: '', - }, - }, - result: { - data: { - users: [ - { - user: { - firstName: 'Deanne', - lastName: 'Marks', - image: null, - _id: '6589389d2caa9d8d69087487', - email: 'testuser8@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - organizationsBlockedBy: [], - joinedOrganizations: [ - { - _id: '6537904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Queens', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 Coffee Street', - line2: 'Apartment 501', - postalCode: '11427', - sortingCode: 'ABC-133', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6637904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Staten Island', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 church Street', - line2: 'Apartment 499', - postalCode: '10301', - sortingCode: 'ABC-122', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6737904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Brooklyn', - countryCode: 'US', - dependentLocality: 'Sample Dependent Locality', - line1: '123 Main Street', - line2: 'Apt 456', - postalCode: '10004', - sortingCode: 'ABC-789', - state: 'NY', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6437904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Bronx', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 Random Street', - line2: 'Apartment 456', - postalCode: '10451', - sortingCode: 'ABC-123', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - ], - __typename: 'User', - }, - appUserProfile: { - _id: '64378abd85308f171cf2993d', - adminFor: [], - isSuperAdmin: false, - createdOrganizations: [], - createdEvents: [], - eventAdmin: [], - __typename: 'AppUserProfile', - }, - __typename: 'UserData', - }, - ], - }, - }, - }, - { - request: { - query: USERS_CONNECTION_LIST, - variables: { - firstName_contains: '', - lastName_contains: '', - }, - }, - result: { - data: { - users: { - user: [ - { - firstName: 'Disha', - lastName: 'Talreja', - image: 'img', - _id: '1', - email: 'disha@email.com', - createdAt: '', - appUserProfile: { - _id: '12', - isSuperAdmin: 'false', - createdOrganizations: { - _id: '345678', - }, - createdEvents: { - _id: '34567890', - }, - }, - organizationsBlockedBy: [], - joinedOrganizations: [], - }, - { - firstName: 'Disha', - lastName: 'Talreja', - image: 'img', - _id: '1', - email: 'disha@email.com', - createdAt: '', - appUserProfile: { - _id: '12', - isSuperAdmin: 'false', - createdOrganizations: { - _id: '345678', - }, - createdEvents: { - _id: '34567890', - }, - }, - organizationsBlockedBy: [], - joinedOrganizations: [], - }, - { - firstName: 'Disha', - lastName: 'Talreja', - image: 'img', - _id: '1', - email: 'disha@email.com', - createdAt: '', - appUserProfile: { - _id: '12', - isSuperAdmin: 'false', - createdOrganizations: { - _id: '345678', - }, - createdEvents: { - _id: '34567890', - }, - }, - organizationsBlockedBy: [], - joinedOrganizations: [], - }, - ], - }, - }, - }, - }, - { - request: { - query: USERS_CONNECTION_LIST, - variables: { - firstName_contains: 'Disha', - lastName_contains: '', - }, - }, - result: { - data: { - users: [ - { - user: { - firstName: 'Deanne', - lastName: 'Marks', - image: null, - _id: '6589389d2caa9d8d69087487', - email: 'testuser8@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - organizationsBlockedBy: [], - joinedOrganizations: [ - { - _id: '6537904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Queens', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 Coffee Street', - line2: 'Apartment 501', - postalCode: '11427', - sortingCode: 'ABC-133', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6637904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Staten Island', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 church Street', - line2: 'Apartment 499', - postalCode: '10301', - sortingCode: 'ABC-122', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6737904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Brooklyn', - countryCode: 'US', - dependentLocality: 'Sample Dependent Locality', - line1: '123 Main Street', - line2: 'Apt 456', - postalCode: '10004', - sortingCode: 'ABC-789', - state: 'NY', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - { - _id: '6437904485008f171cf29924', - name: 'Unity Foundation', - image: null, - address: { - city: 'Bronx', - countryCode: 'US', - dependentLocality: 'Some Dependent Locality', - line1: '123 Random Street', - line2: 'Apartment 456', - postalCode: '10451', - sortingCode: 'ABC-123', - state: 'NYC', - __typename: 'Address', - }, - createdAt: '2023-04-13T05:16:52.827Z', - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - __typename: 'Organization', - }, - ], - __typename: 'User', - }, - appUserProfile: { - _id: '64378abd85308f171cf2993d', - adminFor: [], - isSuperAdmin: false, - createdOrganizations: [], - createdEvents: [], - eventAdmin: [], - __typename: 'AppUserProfile', - }, - __typename: 'UserData', - }, - ], - }, - }, - }, -]; - -const MESSAGE_SENT_TO_CHAT_MOCK = [ - { - request: { - query: MESSAGE_SENT_TO_CHAT, - variables: { - userId: null, - }, - }, - result: { - data: { - messageSentToChat: { - _id: '668ec1f1364e03ac47a151', - createdAt: '2024-07-10T17:16:33.248Z', - messageContent: 'Test ', - type: 'STRING', - replyTo: null, - sender: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: '', - }, - updatedAt: '2024-07-10', - }, - }, - }, - }, - { - request: { - query: MESSAGE_SENT_TO_CHAT, - variables: { - userId: '2', - }, - }, - result: { - data: { - messageSentToChat: { - _id: '668ec1f1df364e03ac47a151', - createdAt: '2024-07-10T17:16:33.248Z', - messageContent: 'Test ', - replyTo: null, - type: 'STRING', - sender: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: '', - }, - updatedAt: '2024-07-10', - }, - }, - }, - }, - { - request: { - query: MESSAGE_SENT_TO_CHAT, - variables: { - userId: '1', - }, - }, - result: { - data: { - messageSentToChat: { - _id: '668ec1f13603ac4697a151', - createdAt: '2024-07-10T17:16:33.248Z', - messageContent: 'Test ', - replyTo: null, - type: 'STRING', - sender: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: '', - }, - updatedAt: '2024-07-10', - }, - }, - }, - }, -]; - -const CHAT_BY_ID_QUERY_MOCK = [ - { - request: { - query: CHAT_BY_ID, - variables: { - id: '1', - }, - }, - result: { - data: { - chatById: { - _id: '1', - createdAt: '2345678903456', - isGroup: false, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: null, - name: '', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - ], - }, - }, - }, - }, - { - request: { - query: CHAT_BY_ID, - variables: { - id: '1', - }, - }, - result: { - data: { - chatById: { - _id: '1', - isGroup: false, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: null, - name: '', - createdAt: '2345678903456', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - ], - }, - }, - }, - }, - { - request: { - query: CHAT_BY_ID, - variables: { - id: '', - }, - }, - result: { - data: { - chatById: { - _id: '1', - isGroup: false, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: null, - name: '', - createdAt: '2345678903456', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - ], - }, - }, - }, - }, -]; - -const CHATS_LIST_MOCK = [ - { - request: { - query: CHATS_LIST, - variables: { - id: null, - }, - }, - result: { - data: { - chatsByUserId: [ - { - _id: '65844efc814dd40fgh03db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - { - _id: '65844efc814ddgh4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: CHATS_LIST, - variables: { - id: '', - }, - }, - result: { - data: { - chatsByUserId: [ - { - _id: '65844ghjefc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - { - _id: 'ujhgtrdtyuiop', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: CHATS_LIST, - variables: { - id: '1', - }, - }, - result: { - data: { - chatsByUserId: [ - { - _id: '65844efc814dhjmkdftyd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - { - _id: '65844ewsedrffc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - ], - }, - }, - }, -]; - -const GROUP_CHAT_BY_ID_QUERY_MOCK = [ - { - request: { - query: CHAT_BY_ID, - variables: { - id: '', - }, - }, - result: { - data: { - chatById: { - _id: '65844efc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - }, - }, - }, -]; - -const CREATE_CHAT_MUTATION = [ - { - request: { - query: CREATE_CHAT, - variables: { - organizationId: '6401ff65ce8e8406b8jygjgf07af2', - userIds: [null], - name: 'Test Group', - isGroup: true, - }, - }, - result: { - data: { - createChat: { - _id: '65844efc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - }, - }, - }, - { - request: { - query: CREATE_CHAT, - variables: { - organizationId: '', - userIds: [null], - name: 'Test Group', - isGroup: true, - }, - }, - result: { - data: { - createChat: { - _id: '65844efc814dd4003db811c4', - isGroup: true, - creator: { - _id: '64378abd85008f171cf2990d', - firstName: 'Wilt', - lastName: 'Shepherd', - image: null, - email: 'testsuperadmin@example.com', - createdAt: '2023-04-13T04:53:17.742Z', - __typename: 'User', - }, - organization: { - _id: 'pw3ertyuiophgfre45678', - name: 'rtyu', - }, - createdAt: '2345678903456', - name: 'Test Group Chat', - messages: [ - { - _id: '345678', - createdAt: '345678908765', - messageContent: 'Hello', - replyTo: null, - type: 'STRING', - sender: { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - }, - ], - users: [ - { - _id: '1', - firstName: 'Disha', - lastName: 'Talreja', - email: 'disha@example.com', - image: '', - }, - { - _id: '2', - firstName: 'Test', - lastName: 'User', - email: 'test@example.com', - image: '', - }, - { - _id: '3', - firstName: 'Test', - lastName: 'User1', - email: 'test1@example.com', - image: '', - }, - { - _id: '4', - firstName: 'Test', - lastName: 'User2', - email: 'test2@example.com', - image: '', - }, - { - _id: '5', - firstName: 'Test', - lastName: 'User4', - email: 'test4@example.com', - image: '', - }, - ], - }, - }, - }, - }, -]; - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -describe('Testing Create Group Chat Modal [User Portal]', () => { - window.HTMLElement.prototype.scrollIntoView = jest.fn(); - - Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); - - test('open and close create new direct chat modal', async () => { - const mock = [ - ...USER_JOINED_ORG_MOCK, - ...GROUP_CHAT_BY_ID_QUERY_MOCK, - ...MESSAGE_SENT_TO_CHAT_MOCK, - ...CHAT_BY_ID_QUERY_MOCK, - ...CHATS_LIST_MOCK, - ...UserConnectionListMock, - ...CREATE_CHAT_MUTATION, - ...CHATS_LIST_MOCK, - ]; - render( - - - - - - - - - , - ); - - await wait(); - - const dropdown = await screen.findByTestId('dropdown'); - expect(dropdown).toBeInTheDocument(); - fireEvent.click(dropdown); - const newGroupChatBtn = await screen.findByTestId('newGroupChat'); - expect(newGroupChatBtn).toBeInTheDocument(); - fireEvent.click(newGroupChatBtn); - - const closeButton = screen.getByRole('button', { name: /close/i }); - expect(closeButton).toBeInTheDocument(); - - fireEvent.click(closeButton); - }); - - test('create new group chat', async () => { - const mock = [ - ...USER_JOINED_ORG_MOCK, - ...GROUP_CHAT_BY_ID_QUERY_MOCK, - ...MESSAGE_SENT_TO_CHAT_MOCK, - ...CHAT_BY_ID_QUERY_MOCK, - ...CHATS_LIST_MOCK, - ...UserConnectionListMock, - ...CREATE_CHAT_MUTATION, - ...CHATS_LIST_MOCK, - ]; - render( - - - - - - - - - , - ); - - await wait(); - - const dropdown = await screen.findByTestId('dropdown'); - expect(dropdown).toBeInTheDocument(); - fireEvent.click(dropdown); - - const newGroupChatBtn = await screen.findByTestId('newGroupChat'); - expect(newGroupChatBtn).toBeInTheDocument(); - - fireEvent.click(newGroupChatBtn); - - await waitFor(async () => { - expect( - await screen.findByTestId('createGroupChatModal'), - ).toBeInTheDocument(); - }); - - const groupTitleInput = screen.getByLabelText( - 'Group name', - ) as HTMLInputElement; - - expect(groupTitleInput).toBeInTheDocument(); - - fireEvent.change(groupTitleInput, { target: { value: 'Test Group' } }); - await waitFor(() => { - expect(groupTitleInput.value).toBe('Test Group'); - }); - - const selectLabel = /select organization/i; - const orgSelect = await screen.findByLabelText('Select Organization'); - // console.log(prettyDOM(document)); - - // act(() => { - // fireEvent.change(orgSelect, { - // target: { value: '6401ff65ce8e8406b8f07af2' }, - // }); - // }) - // fireEvent.change(orgSelect, { - // target: { value: '6401ff65ce8e8406b8f07af2' }, - // }); - - // act(() => { - userEvent.click(orgSelect); - - const optionsPopupEl = await screen.findByRole('listbox', { - name: selectLabel, - }); - - userEvent.click(within(optionsPopupEl).getByText(/any organization/i)); - // }); - - // await waitFor(async () => { - // const option = await screen.findAllByText('Any Organization'); - - // console.log("option", option); - // }); - - const nextBtn = await screen.findByTestId('nextBtn'); - - act(() => { - fireEvent.click(nextBtn); - }); - - const createBtn = await screen.findByTestId('createBtn'); - await waitFor(async () => { - expect(createBtn).toBeInTheDocument(); - }); - - await act(async () => { - fireEvent.click(await screen.findByTestId('createBtn')); - }); - - // await waitFor(() => { - // expect(createBtn).not.toBeInTheDocument(); - // }); - }, 3000); - - test('add and remove user', async () => { - setItem('userId', '1'); - const mock = [ - ...USER_JOINED_ORG_MOCK, - ...GROUP_CHAT_BY_ID_QUERY_MOCK, - ...MESSAGE_SENT_TO_CHAT_MOCK, - ...CHAT_BY_ID_QUERY_MOCK, - ...CHATS_LIST_MOCK, - ...UserConnectionListMock, - ...CREATE_CHAT_MUTATION, - ...CHATS_LIST_MOCK, - ]; - render( - - - - - - - - - , - ); - - await wait(); - - const dropdown = await screen.findByTestId('dropdown'); - expect(dropdown).toBeInTheDocument(); - fireEvent.click(dropdown); - const newGroupChatBtn = await screen.findByTestId('newGroupChat'); - expect(newGroupChatBtn).toBeInTheDocument(); - fireEvent.click(newGroupChatBtn); - - await waitFor(async () => { - expect( - await screen.findByTestId('createGroupChatModal'), - ).toBeInTheDocument(); - }); - - const nextBtn = await screen.findByTestId('nextBtn'); - - act(() => { - fireEvent.click(nextBtn); - }); - - await waitFor(async () => { - const addBtn = await screen.findAllByTestId('addBtn'); - expect(addBtn[0]).toBeInTheDocument(); - }); - - const addBtn = await screen.findAllByTestId('addBtn'); - - fireEvent.click(addBtn[0]); - - const removeBtn = await screen.findAllByText('Remove'); - await waitFor(async () => { - expect(removeBtn[0]).toBeInTheDocument(); - }); - fireEvent.click(removeBtn[0]); - - await waitFor(() => { - expect(addBtn[0]).toBeInTheDocument(); - }); - - const submitBtn = await screen.findByTestId('submitBtn'); - - expect(submitBtn).toBeInTheDocument(); - - const searchInput = (await screen.findByTestId( - 'searchUser', - )) as HTMLInputElement; - expect(searchInput).toBeInTheDocument(); - - fireEvent.change(searchInput, { target: { value: 'Disha' } }); - - expect(searchInput.value).toBe('Disha'); - - fireEvent.click(submitBtn); - - const closeButton = screen.getAllByRole('button', { name: /close/i }); - expect(closeButton[0]).toBeInTheDocument(); - - fireEvent.click(closeButton[0]); - - await wait(500); - }); -}); diff --git a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx index e293675a03..f61dcf620d 100644 --- a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx +++ b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx @@ -1,18 +1,9 @@ -import { - FormControl, - InputLabel, - MenuItem, - Paper, - Select, - TableBody, -} from '@mui/material'; -import type { SelectChangeEvent } from '@mui/material/Select'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { Paper, TableBody } from '@mui/material'; import { Button, Form, Modal } from 'react-bootstrap'; import styles from './CreateGroupChat.module.css'; import type { ApolloQueryResult } from '@apollo/client'; import { useMutation, useQuery } from '@apollo/client'; -import { USER_JOINED_ORGANIZATIONS } from 'GraphQl/Queries/OrganizationQueries'; import useLocalStorage from 'utils/useLocalstorage'; import { CREATE_CHAT } from 'GraphQl/Mutations/OrganizationMutations'; import Table from '@mui/material/Table'; @@ -25,6 +16,11 @@ import type { InterfaceQueryUserListItem } from 'utils/interfaces'; import { USERS_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; import Loader from 'components/Loader/Loader'; import { Search } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import convertToBase64 from 'utils/convertToBase64'; +import Avatar from 'components/Avatar/Avatar'; +import { FiEdit } from 'react-icons/fi'; interface InterfaceCreateGroupChatProps { toggleCreateGroupChatModal: () => void; @@ -35,31 +31,7 @@ interface InterfaceCreateGroupChatProps { id: string; }> | undefined, - ) => Promise>; -} - -interface InterfaceOrganization { - _id: string; - name: string; - image: string; - description: string; - admins: []; - members: []; - address: { - city: string; - countryCode: string; - line1: string; - postalCode: string; - state: string; - }; - membershipRequestStatus: string; - userRegistrationRequired: boolean; - membershipRequests: { - _id: string; - user: { - _id: string; - }; - }[]; + ) => Promise>; } /** @@ -94,12 +66,14 @@ export default function CreateGroupChat({ chatsListRefetch, }: InterfaceCreateGroupChatProps): JSX.Element { const userId: string | null = getItem('userId'); + const { t } = useTranslation('translation', { + keyPrefix: 'userChat', + }); const [createChat] = useMutation(CREATE_CHAT); - const [organizations, setOrganizations] = useState([]); - const [selectedOrganization, setSelectedOrganization] = useState(''); const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); const [userIds, setUserIds] = useState([]); const [addUserModalisOpen, setAddUserModalisOpen] = useState(false); @@ -111,21 +85,11 @@ export default function CreateGroupChat({ const toggleAddUserModal = /* istanbul ignore next */ (): void => setAddUserModalisOpen(!addUserModalisOpen); - const handleChange = (event: SelectChangeEvent): void => { - setSelectedOrganization(event.target.value as string); - }; - - const { data: joinedOrganizationsData } = useQuery( - USER_JOINED_ORGANIZATIONS, - { - variables: { id: userId }, - }, - ); + const { orgId: currentOrg } = useParams(); function reset(): void { setTitle(''); setUserIds([]); - setSelectedOrganization(''); } useEffect(() => { @@ -135,10 +99,11 @@ export default function CreateGroupChat({ async function handleCreateGroupChat(): Promise { await createChat({ variables: { - organizationId: selectedOrganization, + organizationId: currentOrg, userIds: [userId, ...userIds], name: title, isGroup: true, + image: selectedImage, }, }); chatsListRefetch(); @@ -175,13 +140,23 @@ export default function CreateGroupChat({ }); }; - useEffect(() => { - if (joinedOrganizationsData && joinedOrganizationsData.users.length > 0) { - const organizations = - joinedOrganizationsData.users[0]?.user?.joinedOrganizations || []; - setOrganizations(organizations); + const [selectedImage, setSelectedImage] = useState(null); + + const fileInputRef = useRef(null); + + const handleImageClick = (): void => { + fileInputRef?.current?.click(); + }; + + const handleImageChange = async ( + e: React.ChangeEvent, + ): Promise => { + const file = e.target.files?.[0]; + if (file) { + const base64 = await convertToBase64(file); + setSelectedImage(base64); } - }, [joinedOrganizationsData]); + }; return ( <> @@ -195,44 +170,57 @@ export default function CreateGroupChat({ New Group + +
+ {selectedImage ? ( + + ) : ( + + )} + +
- - Select Organization - - - Group name + Title { setTitle(e.target.value); }} /> + + Description + { + setDescription(e.target.value); + }} + /> + )} @@ -358,7 +346,7 @@ export default function CreateGroupChat({ onClick={handleCreateGroupChat} data-testid="createBtn" > - Create + {t('create')}
diff --git a/src/components/UserPortal/DonationCard/DonationCard.test.tsx b/src/components/UserPortal/DonationCard/DonationCard.spec.tsx similarity index 64% rename from src/components/UserPortal/DonationCard/DonationCard.test.tsx rename to src/components/UserPortal/DonationCard/DonationCard.spec.tsx index cddf62dd6c..f4d8473dd1 100644 --- a/src/components/UserPortal/DonationCard/DonationCard.test.tsx +++ b/src/components/UserPortal/DonationCard/DonationCard.spec.tsx @@ -11,6 +11,19 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import DonationCard from './DonationCard'; import { type InterfaceDonationCardProps } from 'screens/UserPortal/Donate/Donate'; +/** + * Unit test for the DonationCard component in the User Portal + * + * This test ensures that the DonationCard component renders correctly when provided with + * the required props. It uses the MockedProvider to mock any GraphQL queries and the + * StaticMockLink to simulate the network layer. The component is wrapped with necessary + * providers such as BrowserRouter, Redux store, and i18n provider to simulate the environment + * in which the component operates. + * + * The test specifically checks if the component renders without errors, though more tests + * can be added in the future to validate interactions and state changes based on user actions. + */ + const link = new StaticMockLink([], true); async function wait(ms = 100): Promise { @@ -31,7 +44,7 @@ const props: InterfaceDonationCardProps = { }; describe('Testing ContactCard Component [User Portal]', () => { - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { render( diff --git a/src/components/UserPortal/EventCard/EventCard.test.tsx b/src/components/UserPortal/EventCard/EventCard.spec.tsx similarity index 94% rename from src/components/UserPortal/EventCard/EventCard.test.tsx rename to src/components/UserPortal/EventCard/EventCard.spec.tsx index 78aa32abcf..150f676447 100644 --- a/src/components/UserPortal/EventCard/EventCard.test.tsx +++ b/src/components/UserPortal/EventCard/EventCard.spec.tsx @@ -11,7 +11,6 @@ import { Provider } from 'react-redux'; import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import userEvent from '@testing-library/user-event'; -import { debug } from 'jest-preview'; import useLocalStorage from 'utils/useLocalstorage'; const { setItem } = useLocalStorage(); @@ -66,7 +65,7 @@ describe('Testing Event Card In User portal', () => { ], }; - test('The card should be rendered properly, and all the details should be displayed correct', async () => { + it('The card should be rendered properly, and all the details should be displayed correct', async () => { const { queryByText } = render( @@ -79,7 +78,6 @@ describe('Testing Event Card In User portal', () => { , ); - debug(); await waitFor(() => expect(queryByText('Test Event')).toBeInTheDocument()); await waitFor(() => expect(queryByText('This is a test event')).toBeInTheDocument(), @@ -105,7 +103,7 @@ describe('Testing Event Card In User portal', () => { await waitFor(() => expect(queryByText('Register')).toBeInTheDocument()); }); - test('When the user is already registered', async () => { + it('When the user is already registered', async () => { setItem('userId', '234'); const { queryByText } = render( @@ -124,7 +122,7 @@ describe('Testing Event Card In User portal', () => { ); }); - test('Handle register should work properly', async () => { + it('Handle register should work properly', async () => { setItem('userId', '456'); const { queryByText } = render( @@ -173,7 +171,7 @@ describe('Event card when start and end time are not given', () => { ], }; - test('Card is rendered correctly', async () => { + it('Card is rendered correctly', async () => { const { container } = render( diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx b/src/components/UserPortal/OrganizationCard/OrganizationCard.spec.tsx similarity index 77% rename from src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx rename to src/components/UserPortal/OrganizationCard/OrganizationCard.spec.tsx index 77516ddc7a..1aa8c8462a 100644 --- a/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.spec.tsx @@ -18,15 +18,33 @@ import useLocalStorage from 'utils/useLocalstorage'; import { SEND_MEMBERSHIP_REQUEST, JOIN_PUBLIC_ORGANIZATION, + CANCEL_MEMBERSHIP_REQUEST, } from 'GraphQl/Mutations/OrganizationMutations'; import { toast } from 'react-toastify'; - +import { vi } from 'vitest'; + +/** + * Unit tests for the OrganizationCard component in the User Portal + * + * These tests validate the behavior and rendering of the OrganizationCard component. + * The tests ensure the component displays properly with various states and that interactions + * such as sending membership requests and visiting organizations work as expected. + * + * 1. **Component should be rendered properly**: Tests if the component renders correctly with the provided props. + * 2. **Component should render properly with an image**: Verifies the component's behavior when an organization image is available. + * 3. **Visit organization**: Simulates a click on the "manage" button and verifies that the user is redirected to the correct organization page. + * 4. **Send membership request**: Tests if the membership request is successfully sent and verifies the success toast message. + * 5. **Send membership request to a public organization**: Validates sending a membership request to a public organization and verifies multiple success toast messages. + * 6. **Withdraw membership request**: Simulates withdrawing a membership request and verifies that the button works as expected. + * + * Mocked GraphQL queries and mutations are used to simulate the backend behavior for testing. + */ const { getItem } = useLocalStorage(); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -146,6 +164,21 @@ const MOCKS = [ }, }, }, + { + request: { + query: CANCEL_MEMBERSHIP_REQUEST, + variables: { + membershipRequestId: '56gheqyr7deyfuiwfewifruy8', + }, + }, + result: { + data: { + cancelMembershipRequest: { + _id: '56gheqyr7deyfuiwfewifruy8', + }, + }, + }, + }, ]; const link = new StaticMockLink(MOCKS, true); @@ -189,7 +222,7 @@ let props = { }; describe('Testing OrganizationCard Component [User Portal]', () => { - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { render( @@ -205,7 +238,7 @@ describe('Testing OrganizationCard Component [User Portal]', () => { await wait(); }); - test('Component should be rendered properly if organization Image is not undefined', async () => { + it('Component should be rendered properly if organization Image is not undefined', async () => { props = { ...props, image: 'organizationImage', @@ -226,7 +259,7 @@ describe('Testing OrganizationCard Component [User Portal]', () => { await wait(); }); - test('Visit organization', async () => { + it('Visit organization', async () => { const cardProps = { ...props, id: '3', @@ -258,7 +291,7 @@ describe('Testing OrganizationCard Component [User Portal]', () => { expect(window.location.pathname).toBe(`/user/organization/${cardProps.id}`); }); - test('Send membership request', async () => { + it('Send membership request', async () => { props = { ...props, image: 'organizationImage', @@ -286,7 +319,7 @@ describe('Testing OrganizationCard Component [User Portal]', () => { expect(toast.success).toHaveBeenCalledWith('MembershipRequestSent'); }); - test('send membership request to public org', async () => { + it('send membership request to public org', async () => { const cardProps = { ...props, id: '2', @@ -316,13 +349,21 @@ describe('Testing OrganizationCard Component [User Portal]', () => { expect(toast.success).toHaveBeenCalledTimes(2); }); - test('withdraw membership request', async () => { + it('withdraw membership request', async () => { const cardProps = { ...props, id: '3', image: 'organizationImage', userRegistrationRequired: true, membershipRequestStatus: 'pending', + membershipRequests: [ + { + _id: '56gheqyr7deyfuiwfewifruy8', + user: { + _id: getItem('userId'), + }, + }, + ], }; render( diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.spec.tsx similarity index 81% rename from src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx rename to src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.spec.tsx index 038ff626df..7f9e8e52bb 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.spec.tsx @@ -1,6 +1,5 @@ import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import 'jest-localstorage-mock'; import { render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; @@ -17,6 +16,28 @@ import { PLUGIN_SUBSCRIPTION } from 'GraphQl/Mutations/mutations'; import { createMemoryHistory } from 'history'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; + +/** + * Unit tests for the OrganizationNavbar component. + * + * These tests validate the rendering and behavior of the component, ensuring it renders correctly, + * handles user interactions, and manages language and plugin updates. + * + * 1. **Component rendering**: Verifies correct rendering with provided props and organization details. + * 2. **Navigation on plugin click**: Simulates navigation when a plugin link is clicked. + * 3. **Language switch to English**: Tests if the language changes to English. + * 4. **Language switch to French**: Tests if the language changes to French. + * 5. **Language switch to Hindi**: Tests if the language changes to Hindi. + * 6. **Language switch to Spanish**: Tests if the language changes to Spanish. + * 7. **Language switch to Chinese**: Tests if the language changes to Chinese. + * 8. **Rendering plugins from localStorage**: Verifies correct rendering of plugins from localStorage. + * 9. **Plugin removal on uninstallation**: Ensures plugins are removed when uninstalled for the organization. + * 10. **Rendering plugins when not uninstalled**: Ensures plugins render if not uninstalled. + * 11. **No changes for unmatched plugin**: Ensures no changes when an unrecognized plugin update occurs. + * + * Mocked GraphQL queries and subscriptions simulate backend behavior. + */ const { setItem, removeItem } = useLocalStorage(); @@ -167,23 +188,26 @@ const navbarProps = { currentPage: 'home', }; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: organizationId }), -})); +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: organizationId }), + }; +}); describe('Testing OrganizationNavbar Component [User Portal]', () => { Object.defineProperty(window, 'matchMedia', { writable: true, - value: jest.fn().mockImplementation((query) => ({ + value: vi.fn().mockImplementation((query) => ({ matches: false, media: query, onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), + addListener: vi.fn(), // Deprecated + removeListener: vi.fn(), // Deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), })), }); @@ -193,7 +217,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { }); }); - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { render( @@ -217,7 +241,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { // expect(screen.getByText('Chat')).toBeInTheDocument(); }); - test('should navigate correctly on clicking a plugin', async () => { + it('should navigate correctly on clicking a plugin', async () => { const history = createMemoryHistory(); render( @@ -240,7 +264,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { expect(history.location.pathname).toBe(`/user/people/${organizationId}`); }); - test('The language is switched to English', async () => { + it('The language is switched to English', async () => { render( @@ -270,7 +294,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { // expect(screen.getByText('Chat')).toBeInTheDocument(); }); - test('The language is switched to fr', async () => { + it('The language is switched to fr', async () => { render( @@ -294,7 +318,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('fr'); }); - test('The language is switched to hi', async () => { + it('The language is switched to hi', async () => { render( @@ -318,7 +342,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('hi'); }); - test('The language is switched to sp', async () => { + it('The language is switched to sp', async () => { render( @@ -342,7 +366,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('sp'); }); - test('The language is switched to zh', async () => { + it('The language is switched to zh', async () => { render( @@ -366,7 +390,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('zh'); }); - test('Component should be rendered properly if plugins are present in localStorage', async () => { + it('Component should be rendered properly if plugins are present in localStorage', async () => { setItem('talawaPlugins', JSON.stringify(testPlugins)); render( @@ -390,7 +414,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { removeItem('talawaPlugins'); }); - test('should remove plugin if uninstalledOrgs contains organizationId', async () => { + it('should remove plugin if uninstalledOrgs contains organizationId', async () => { setItem('talawaPlugins', JSON.stringify(testPlugins)); render( @@ -412,7 +436,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { }); }); - test('should render plugin if uninstalledOrgs does not contain organizationId', async () => { + it('should render plugin if uninstalledOrgs does not contain organizationId', async () => { setItem('talawaPlugins', JSON.stringify(testPlugins)); render( @@ -434,7 +458,7 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { }); }); - test('should do nothing if pluginName is not found in the rendered plugins', async () => { + it('should do nothing if pluginName is not found in the rendered plugins', async () => { render( diff --git a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.spec.tsx similarity index 74% rename from src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx rename to src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.spec.tsx index cddac285fd..2559eaae14 100644 --- a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx +++ b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.spec.tsx @@ -13,6 +13,20 @@ import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import OrganizationSidebar from './OrganizationSidebar'; +import { vi } from 'vitest'; + +/** + * Unit tests for the OrganizationSidebar component in the User Portal. + * + * These tests validate the rendering and behavior of the OrganizationSidebar component, + * ensuring that it displays correct content based on the availability of members and events. + * + * 1. **Component renders properly when members and events lists are empty**: Verifies the correct display of "No Members to show" and "No Events to show" when both lists are empty. + * 2. **Component renders properly when events list is not empty**: Tests that the events section is rendered correctly when events are available, and "No Events to show" is not displayed. + * 3. **Component renders properly when members list is not empty**: Verifies the correct display of members when available, ensuring "No Members to show" is not displayed. + * + * Mocked GraphQL queries simulate backend responses for members and events lists. + */ const MOCKS = [ { @@ -94,13 +108,17 @@ async function wait(ms = 100): Promise { }); } let mockId = ''; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockId }), -})); + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: mockId }), + }; +}); describe('Testing OrganizationSidebar Component [User Portal]', () => { - test('Component should be rendered properly when members and events list is empty', async () => { + it('Component should be rendered properly when members and events list is empty', async () => { render( @@ -119,7 +137,7 @@ describe('Testing OrganizationSidebar Component [User Portal]', () => { expect(screen.queryByText('No Events to show')).toBeInTheDocument(); }); - test('Component should be rendered properly when events list is not empty', async () => { + it('Component should be rendered properly when events list is not empty', async () => { mockId = 'events'; render( @@ -139,7 +157,7 @@ describe('Testing OrganizationSidebar Component [User Portal]', () => { expect(screen.queryByText('Event')).toBeInTheDocument(); }); - test('Component should be rendered properly when members list is not empty', async () => { + it('Component should be rendered properly when members list is not empty', async () => { mockId = 'members'; render( diff --git a/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx b/src/components/UserPortal/PeopleCard/PeopleCard.spec.tsx similarity index 65% rename from src/components/UserPortal/PeopleCard/PeopleCard.test.tsx rename to src/components/UserPortal/PeopleCard/PeopleCard.spec.tsx index fd5d6c7f93..c725a8d45a 100644 --- a/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx +++ b/src/components/UserPortal/PeopleCard/PeopleCard.spec.tsx @@ -10,6 +10,18 @@ import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import PeopleCard from './PeopleCard'; +/** + * Unit tests for the PeopleCard component in the User Portal. + * + * These tests ensure that the PeopleCard component renders correctly with and without an image, + * validating that all information (name, role, email, etc.) is displayed as expected. + * + * 1. **Component renders properly**: Verifies that the component renders correctly with the given props (name, email, role, etc.). + * 2. **Component renders properly if the person image is provided**: Ensures the component correctly displays the image when a valid image URL is passed in the props. + * + * Mocked GraphQL queries are used to simulate backend behavior, though no queries are required for these tests. + */ + const link = new StaticMockLink([], true); async function wait(ms = 100): Promise { @@ -30,7 +42,7 @@ let props = { }; describe('Testing PeopleCard Component [User Portal]', () => { - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { render( @@ -46,7 +58,7 @@ describe('Testing PeopleCard Component [User Portal]', () => { await wait(); }); - test('Component should be rendered properly if person image is not undefined', async () => { + it('Component should be rendered properly if person image is not undefined', async () => { props = { ...props, image: 'personImage', diff --git a/src/components/UserPortal/PostCard/PostCard.module.css b/src/components/UserPortal/PostCard/PostCard.module.css deleted file mode 100644 index e8edc2d82c..0000000000 --- a/src/components/UserPortal/PostCard/PostCard.module.css +++ /dev/null @@ -1,183 +0,0 @@ -.cardStyles { - width: 20rem; - background-color: white; - padding: 0; - border: none !important; - outline: none !important; -} - -.cardHeader { - display: flex; - width: 100%; - padding-inline: 0; - padding-block: 0; - flex-direction: row; - gap: 0.5rem; - align-items: center; - background-color: white; - border-bottom: 1px solid #dddddd; -} - -.creator { - display: flex; - width: 100%; - padding-inline: 1rem; - padding-block: 0; - flex-direction: row; - gap: 0.5rem; - align-items: center; -} -.creator p { - margin-bottom: 0; - font-weight: 500; -} -.creator svg { - width: 2rem; - height: 2rem; -} - -.customToggle { - padding: 0; - background: none; - border: none; - margin-right: 1rem; - --bs-btn-active-bg: none; -} -.customToggle svg { - color: black; -} - -.customToggle::after { - content: none; -} -.customToggle:hover, -.customToggle:focus, -.customToggle:active { - background: none; - border: none; -} -.customToggle svg { - color: black; -} - -.cardBody div { - padding: 0.5rem; -} - -.imageContainer { - max-width: 100%; -} - -.cardTitle { - --max-lines: 1; - display: -webkit-box; - overflow: hidden; - -webkit-box-orient: vertical; - -webkit-line-clamp: var(--max-lines); - - font-size: 1.3rem !important; - font-weight: 600; -} - -.date { - font-weight: 600; -} - -.cardText { - --max-lines: 2; - display: -webkit-box; - overflow: hidden; - -webkit-box-orient: vertical; - -webkit-line-clamp: var(--max-lines); - - padding-top: 0; - font-weight: 300; - margin-top: 0.7rem !important; - text-align: justify; -} - -.viewBtn { - display: flex; - justify-content: flex-end; - margin: 0.5rem; -} -.viewBtn Button { - padding-inline: 1rem; -} - -.cardActions { - display: flex; - flex-direction: row; - align-items: center; - gap: 1px; - justify-content: flex-end; -} - -.cardActionBtn { - background-color: rgba(0, 0, 0, 0); - padding: 0; - border: none; - color: black; -} - -.cardActionBtn:hover { - background-color: ghostwhite; - border: none; - color: black !important; -} - -.creatorNameModal { - display: flex; - flex-direction: row; - gap: 5px; - align-items: center; - margin-bottom: 10px; -} - -.modalActions { - display: flex; - flex-direction: row; - align-items: center; - gap: 1rem; - margin: 5px 0px; -} - -.textModal { - margin-top: 10px; -} - -.colorPrimary { - background: #31bb6b; - color: white; - cursor: pointer; -} - -.commentContainer { - overflow: auto; - max-height: 18rem; - padding-bottom: 1rem; -} - -.modalFooter { - background-color: white; - position: absolute; - width: calc(100% - 1rem); - padding-block: 0.5rem; - display: flex; - flex-direction: column; - border-top: 1px solid #dddddd; - bottom: 0; - right: 0.5rem; - margin-left: 1rem; -} - -.inputArea { - border: none; - outline: none; - background-color: #f1f3f6; -} - -.postImage { - height: 300px; - object-fit: cover; -} diff --git a/src/components/UserPortal/PostCard/PostCard.test.tsx b/src/components/UserPortal/PostCard/PostCard.spec.tsx similarity index 90% rename from src/components/UserPortal/PostCard/PostCard.test.tsx rename to src/components/UserPortal/PostCard/PostCard.spec.tsx index 1b7708b384..fe3fcf3dc1 100644 --- a/src/components/UserPortal/PostCard/PostCard.test.tsx +++ b/src/components/UserPortal/PostCard/PostCard.spec.tsx @@ -21,14 +21,33 @@ import { UPDATE_POST_MUTATION, } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; + +/** + * Unit tests for the PostCard component in the User Portal. + * + * These tests ensure the PostCard component behaves as expected: + * + * 1. **Component rendering**: Verifies correct rendering with props like title, text, and creator info. + * 2. **Dropdown functionality**: Tests the dropdown for editing and deleting posts. + * 3. **Edit post**: Ensures the post can be edited with a success message. + * 4. **Delete post**: Verifies post deletion works with a success message. + * 5. **Like/unlike post**: Ensures the UI updates when a user likes or unlikes a post. + * 6. **Post image**: Verifies post image rendering. + * 7. **Create comment**: Ensures a comment is created successfully. + * 8. **Like/unlike comment**: Tests liking/unliking comments. + * 9. **Comment modal**: Verifies the comment modal appears when clicked. + * + * Mocked GraphQL data is used for simulating backend behavior. + */ const { setItem, getItem } = useLocalStorage(); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - error: jest.fn(), - info: jest.fn(), - success: jest.fn(), + error: vi.fn(), + info: vi.fn(), + success: vi.fn(), }, })); @@ -164,7 +183,7 @@ async function wait(ms = 100): Promise { const link = new StaticMockLink(MOCKS, true); describe('Testing PostCard Component [User Portal]', () => { - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { const cardProps = { id: 'postId', userImage: 'image.png', @@ -218,7 +237,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '2', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -236,7 +255,7 @@ describe('Testing PostCard Component [User Portal]', () => { await wait(); }); - test('Dropdown component should be rendered properly', async () => { + it('Dropdown component should be rendered properly', async () => { setItem('userId', '2'); const cardProps = { @@ -263,7 +282,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '2', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -285,7 +304,7 @@ describe('Testing PostCard Component [User Portal]', () => { expect(screen.getByText('Delete')).toBeInTheDocument(); }); - test('Edit post should work properly', async () => { + it('Edit post should work properly', async () => { setItem('userId', '2'); const cardProps = { @@ -312,7 +331,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '2', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -341,7 +360,7 @@ describe('Testing PostCard Component [User Portal]', () => { expect(toast.success).toHaveBeenCalledWith('Post updated Successfully'); }); - test('Delete post should work properly', async () => { + it('Delete post should work properly', async () => { setItem('userId', '2'); const cardProps = { @@ -368,7 +387,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '2', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -393,7 +412,7 @@ describe('Testing PostCard Component [User Portal]', () => { ); }); - test('Component should be rendered properly if user has liked the post', async () => { + it('Component should be rendered properly if user has liked the post', async () => { const beforeUserId = getItem('userId'); setItem('userId', '2'); @@ -421,7 +440,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '2', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -443,7 +462,7 @@ describe('Testing PostCard Component [User Portal]', () => { } }); - test('Component should be rendered properly if user unlikes a post', async () => { + it('Component should be rendered properly if user unlikes a post', async () => { const beforeUserId = getItem('userId'); setItem('userId', '2'); @@ -471,7 +490,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '2', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -496,7 +515,7 @@ describe('Testing PostCard Component [User Portal]', () => { } }); - test('Component should be rendered properly if user likes a post', async () => { + it('Component should be rendered properly if user likes a post', async () => { const beforeUserId = getItem('userId'); setItem('userId', '2'); @@ -524,7 +543,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '1', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -549,7 +568,7 @@ describe('Testing PostCard Component [User Portal]', () => { } }); - test('Component should be rendered properly if post image is defined', async () => { + it('Component should be rendered properly if post image is defined', async () => { const cardProps = { id: '', userImage: 'image.png', @@ -574,7 +593,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '1', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -592,7 +611,7 @@ describe('Testing PostCard Component [User Portal]', () => { await wait(); }); - test('Comment is created successfully after create comment button is clicked.', async () => { + it('Comment is created successfully after create comment button is clicked.', async () => { const cardProps = { id: '1', userImage: 'image.png', @@ -617,7 +636,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '1', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( @@ -701,7 +720,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '1', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; const beforeUserId = getItem('userId'); setItem('userId', '2'); @@ -788,7 +807,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '1', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; const beforeUserId = getItem('userId'); setItem('userId', '1'); @@ -815,7 +834,7 @@ describe('Testing PostCard Component [User Portal]', () => { setItem('userId', beforeUserId); } }); - test('Comment modal pops when show comments button is clicked.', async () => { + it('Comment modal pops when show comments button is clicked.', async () => { const cardProps = { id: '', userImage: 'image.png', @@ -840,7 +859,7 @@ describe('Testing PostCard Component [User Portal]', () => { id: '1', }, ], - fetchPosts: jest.fn(), + fetchPosts: vi.fn(), }; render( diff --git a/src/components/UserPortal/PostCard/PostCard.tsx b/src/components/UserPortal/PostCard/PostCard.tsx index f8fcdaebca..1ac693b85a 100644 --- a/src/components/UserPortal/PostCard/PostCard.tsx +++ b/src/components/UserPortal/PostCard/PostCard.tsx @@ -33,7 +33,7 @@ import { import CommentCard from '../CommentCard/CommentCard'; import { errorHandler } from 'utils/errorHandler'; import useLocalStorage from 'utils/useLocalstorage'; -import styles from './PostCard.module.css'; +import styles from './../../../style/app.module.css'; import UserDefault from '../../../assets/images/defaultImg.png'; interface InterfaceCommentCardProps { @@ -121,13 +121,12 @@ export default function postCard(props: InterfacePostCard): JSX.Element { postId: props.id, }, }); - /* istanbul ignore next */ + if (data) { setLikes((likes) => likes - 1); setIsLikedByUser(false); } } catch (error: unknown) { - /* istanbul ignore next */ toast.error(error as string); } } else { @@ -137,13 +136,12 @@ export default function postCard(props: InterfacePostCard): JSX.Element { postId: props.id, }, }); - /* istanbul ignore next */ + if (data) { setLikes((likes) => likes + 1); setIsLikedByUser(true); } } catch (error: unknown) { - /* istanbul ignore next */ toast.error(error as string); } } @@ -205,7 +203,6 @@ export default function postCard(props: InterfacePostCard): JSX.Element { }, }); - /* istanbul ignore next */ if (createEventData) { setCommentInput(''); setNumComments((numComments) => numComments + 1); @@ -228,7 +225,6 @@ export default function postCard(props: InterfacePostCard): JSX.Element { setComments([...comments, newComment]); } } catch (error: unknown) { - /* istanbul ignore next */ errorHandler(t, error); } }; @@ -247,7 +243,6 @@ export default function postCard(props: InterfacePostCard): JSX.Element { toggleEditPost(); toast.success(tCommon('updatedSuccessfully', { item: 'Post' }) as string); } catch (error: unknown) { - /* istanbul ignore next */ errorHandler(t, error); } }; @@ -264,7 +259,6 @@ export default function postCard(props: InterfacePostCard): JSX.Element { props.fetchPosts(); // Refresh the posts toast.success('Successfully deleted the Post.'); } catch (error: unknown) { - /* istanbul ignore next */ errorHandler(t, error); } }; @@ -272,7 +266,7 @@ export default function postCard(props: InterfacePostCard): JSX.Element { return (
- +

{postCreator}

@@ -316,7 +310,7 @@ export default function postCard(props: InterfacePostCard): JSX.Element { } /> - + {props.title} @@ -355,7 +349,7 @@ export default function postCard(props: InterfacePostCard): JSX.Element {
-
+

{postCreator}

diff --git a/src/components/UserPortal/PromotedPost/PromotedPost.test.tsx b/src/components/UserPortal/PromotedPost/PromotedPost.spec.tsx similarity index 77% rename from src/components/UserPortal/PromotedPost/PromotedPost.test.tsx rename to src/components/UserPortal/PromotedPost/PromotedPost.spec.tsx index 6ec8ec5de7..dc8bb14e2d 100644 --- a/src/components/UserPortal/PromotedPost/PromotedPost.test.tsx +++ b/src/components/UserPortal/PromotedPost/PromotedPost.spec.tsx @@ -10,6 +10,18 @@ import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import PromotedPost from './PromotedPost'; +/** + * Unit tests for the PromotedPost component. + * + * 1. **Render check**: Verifies the component renders correctly with props like title and image. + * 2. **Image prop check**: Tests if the component renders correctly with an image. + * 3. **Icon display**: Ensures the icon (StarPurple500Icon) is displayed. + * 4. **Text display**: Checks that the post title is displayed correctly. + * 5. **Image display**: Verifies the correct image is displayed when the image prop is set. + * + * GraphQL data is mocked for backend simulation. + */ + const link = new StaticMockLink([], true); async function wait(ms = 100): Promise { @@ -27,7 +39,7 @@ let props = { }; describe('Testing PromotedPost Test', () => { - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { render( @@ -43,7 +55,7 @@ describe('Testing PromotedPost Test', () => { await wait(); }); - test('Component should be rendered properly if prop image is not undefined', async () => { + it('Component should be rendered properly if prop image is not undefined', async () => { props = { ...props, image: 'promotedPostImage', @@ -65,7 +77,7 @@ describe('Testing PromotedPost Test', () => { }); }); -test('Component should display the icon correctly', async () => { +it('Component should display the icon correctly', async () => { const { queryByTestId } = render( @@ -84,7 +96,7 @@ test('Component should display the icon correctly', async () => { }); }); -test('Component should display the text correctly', async () => { +it('Component should display the text correctly', async () => { const { queryAllByText } = render( @@ -103,7 +115,7 @@ test('Component should display the text correctly', async () => { }); }); -test('Component should display the image correctly', async () => { +it('Component should display the image correctly', async () => { props = { ...props, image: 'promotedPostImage', diff --git a/src/components/UserPortal/Register/Register.test.tsx b/src/components/UserPortal/Register/Register.spec.tsx similarity index 80% rename from src/components/UserPortal/Register/Register.test.tsx rename to src/components/UserPortal/Register/Register.spec.tsx index 1883d60da3..7ca65f8f0d 100644 --- a/src/components/UserPortal/Register/Register.test.tsx +++ b/src/components/UserPortal/Register/Register.spec.tsx @@ -12,6 +12,22 @@ import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import Register from './Register'; import { toast } from 'react-toastify'; +import { vi } from 'vitest'; + +/** + * Unit tests for the Register component. + * + * 1. **Render test**: Verifies proper rendering of the Register component. + * 2. **Mode switch to Login**: Ensures that clicking the "setLoginBtn" changes mode to 'login'. + * 3. **Empty email validation**: Checks if toast.error is triggered for empty email. + * 4. **Empty password validation**: Ensures toast.error is called for empty password. + * 5. **Empty first name validation**: Ensures toast.error is called if first name is missing. + * 6. **Empty last name validation**: Verifies toast.error is triggered if last name is missing. + * 7. **Password mismatch validation**: Verifies toast.error is shown if confirm password doesn't match. + * 8. **Successful registration**: Confirms that toast.success is called when valid credentials are entered. + * + * GraphQL mock data is used for testing user registration functionality. + */ const MOCKS = [ { @@ -56,22 +72,22 @@ async function wait(ms = 100): Promise { }); } -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); -const setCurrentMode: React.Dispatch> = jest.fn(); +const setCurrentMode: React.Dispatch> = vi.fn(); const props = { setCurrentMode, }; describe('Testing Register Component [User Portal]', () => { - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { render( @@ -87,7 +103,7 @@ describe('Testing Register Component [User Portal]', () => { await wait(); }); - test('Expect the mode to be changed to Login', async () => { + it('Expect the mode to be changed to Login', async () => { render( @@ -107,7 +123,7 @@ describe('Testing Register Component [User Portal]', () => { expect(setCurrentMode).toHaveBeenCalledWith('login'); }); - test('Expect toast.error to be called if email input is empty', async () => { + it('Expect toast.error to be called if email input is empty', async () => { render( @@ -127,7 +143,7 @@ describe('Testing Register Component [User Portal]', () => { expect(toast.error).toHaveBeenCalledWith('Please enter valid details.'); }); - test('Expect toast.error to be called if password input is empty', async () => { + it('Expect toast.error to be called if password input is empty', async () => { render( @@ -148,7 +164,7 @@ describe('Testing Register Component [User Portal]', () => { expect(toast.error).toHaveBeenCalledWith('Please enter valid details.'); }); - test('Expect toast.error to be called if first name input is empty', async () => { + it('Expect toast.error to be called if first name input is empty', async () => { render( @@ -172,7 +188,7 @@ describe('Testing Register Component [User Portal]', () => { expect(toast.error).toHaveBeenCalledWith('Please enter valid details.'); }); - test('Expect toast.error to be called if last name input is empty', async () => { + it('Expect toast.error to be called if last name input is empty', async () => { render( @@ -228,7 +244,7 @@ describe('Testing Register Component [User Portal]', () => { ); }); - test('Expect toast.success to be called if valid credentials are entered.', async () => { + it('Expect toast.success to be called if valid credentials are entered.', async () => { render( diff --git a/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.test.tsx b/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.spec.tsx similarity index 79% rename from src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.test.tsx rename to src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.spec.tsx index 93b71b14f1..8a059b2b63 100644 --- a/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.test.tsx +++ b/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.spec.tsx @@ -1,96 +1,106 @@ -import React from 'react'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { render, screen, waitFor } from '@testing-library/react'; -import SecuredRouteForUser from './SecuredRouteForUser'; -import useLocalStorage from 'utils/useLocalstorage'; - -const { setItem } = useLocalStorage(); - -describe('SecuredRouteForUser', () => { - test('renders the route when the user is logged in', () => { - // Set the 'IsLoggedIn' value to 'TRUE' in localStorage to simulate a logged-in user and do not set 'AdminFor' so that it remains undefined. - setItem('IsLoggedIn', 'TRUE'); - - render( - - - }> - - Organizations Component -
- } - /> - - - , - ); - - expect(screen.getByTestId('organizations-content')).toBeInTheDocument(); - }); - - test('redirects to /user when the user is not logged in', async () => { - // Set the user as not logged in in local storage - setItem('IsLoggedIn', 'FALSE'); - - render( - - - User Login Page
} /> - }> - - Organizations Component -
- } - /> - - - , - ); - - await waitFor(() => { - expect(screen.getByText('User Login Page')).toBeInTheDocument(); - }); - }); - - test('renders the route when the user is logged in and user is ADMIN', () => { - // Set the 'IsLoggedIn' value to 'TRUE' in localStorage to simulate a logged-in user and set 'AdminFor' to simulate ADMIN of some Organization. - setItem('IsLoggedIn', 'TRUE'); - setItem('AdminFor', [ - { - _id: '6537904485008f171cf29924', - __typename: 'Organization', - }, - ]); - - render( - - - Oops! The Page you requested was not found!} - /> - }> - - Organizations Component - - } - /> - - - , - ); - - expect( - screen.getByText(/Oops! The Page you requested was not found!/i), - ).toBeTruthy(); - }); -}); +import React from 'react'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen, waitFor } from '@testing-library/react'; +import SecuredRouteForUser from './SecuredRouteForUser'; +import useLocalStorage from 'utils/useLocalstorage'; + +/** + * Unit tests for SecuredRouteForUser component: + * + * 1. **Logged-in user**: Verifies that the route renders when 'IsLoggedIn' is set to 'TRUE'. + * 2. **Not logged-in user**: Ensures redirection to the login page when 'IsLoggedIn' is 'FALSE'. + * 3. **Logged-in user with admin access**: Checks that the route renders for a logged-in user with 'AdminFor' set (i.e., admin of an organization). + * + * LocalStorage values like 'IsLoggedIn' and 'AdminFor' are set to simulate different user states. + */ + +const { setItem } = useLocalStorage(); + +describe('SecuredRouteForUser', () => { + it('renders the route when the user is logged in', () => { + // Set the 'IsLoggedIn' value to 'TRUE' in localStorage to simulate a logged-in user and do not set 'AdminFor' so that it remains undefined. + setItem('IsLoggedIn', 'TRUE'); + + render( + + + }> + + Organizations Component + + } + /> + + + , + ); + + expect(screen.getByTestId('organizations-content')).toBeInTheDocument(); + }); + + it('redirects to /user when the user is not logged in', async () => { + // Set the user as not logged in in local storage + setItem('IsLoggedIn', 'FALSE'); + + render( + + + User Login Page} /> + }> + + Organizations Component + + } + /> + + + , + ); + + await waitFor(() => { + expect(screen.getByText('User Login Page')).toBeInTheDocument(); + }); + }); + + it('renders the route when the user is logged in and user is ADMIN', () => { + // Set the 'IsLoggedIn' value to 'TRUE' in localStorage to simulate a logged-in user and set 'AdminFor' to simulate ADMIN of some Organization. + setItem('IsLoggedIn', 'TRUE'); + setItem('AdminFor', [ + { + _id: '6537904485008f171cf29924', + __typename: 'Organization', + }, + ]); + + render( + + + Oops! The Page you requested was not found!} + /> + }> + + Organizations Component + + } + /> + + + , + ); + + expect( + screen.getByText(/Oops! The Page you requested was not found!/i), + ).toBeTruthy(); + }); +}); diff --git a/src/components/UserPortal/StartPostModal/StartPostModal.test.tsx b/src/components/UserPortal/StartPostModal/StartPostModal.spec.tsx similarity index 71% rename from src/components/UserPortal/StartPostModal/StartPostModal.test.tsx rename to src/components/UserPortal/StartPostModal/StartPostModal.spec.tsx index c34f3a2e9e..2d9024bcd3 100644 --- a/src/components/UserPortal/StartPostModal/StartPostModal.test.tsx +++ b/src/components/UserPortal/StartPostModal/StartPostModal.spec.tsx @@ -12,12 +12,26 @@ import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; import StartPostModal from './StartPostModal'; - -jest.mock('react-toastify', () => ({ +import { vi } from 'vitest'; + +/** + * Unit tests for StartPostModal component: + * + * 1. **Rendering StartPostModal**: Verifies that the modal renders correctly when the `show` prop is set to `true`. + * 2. **Invalid post submission**: Ensures that when the post body is empty, an error toast is shown with the appropriate message ("Can't create a post with an empty body"). + * 3. **Valid post submission**: Checks that a post with valid text triggers an info toast, and simulates the creation of a post with the message "Processing your post. Please wait." + * 4. **User image null**: Confirms that when the user image is null, a default image is displayed instead. + * 5. **User image not null**: Verifies that when the user image is provided, the correct user image is shown. + * + * Mocked GraphQL mutation (`CREATE_POST_MUTATION`) and toast notifications are used to simulate the post creation process. + * The `renderStartPostModal` function is used to render the modal with different user states and input values. + */ + +vi.mock('react-toastify', () => ({ toast: { - error: jest.fn(), - info: jest.fn(), - success: jest.fn(), + error: vi.fn(), + info: vi.fn(), + success: vi.fn(), }, })); @@ -62,8 +76,8 @@ const renderStartPostModal = ( ): RenderResult => { const cardProps = { show: visibility, - onHide: jest.fn(), - fetchPosts: jest.fn(), + onHide: vi.fn(), + fetchPosts: vi.fn(), userData: { user: { __typename: 'User', @@ -113,18 +127,18 @@ const renderStartPostModal = ( describe('Testing StartPostModal Component: User Portal', () => { afterAll(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('Check if StartPostModal renders properly', async () => { + it('Check if StartPostModal renders properly', async () => { renderStartPostModal(true, null); const modal = await screen.findByTestId('startPostModal'); expect(modal).toBeInTheDocument(); }); - test('On invalid post submission with empty body Error toast should be shown', async () => { - const toastSpy = jest.spyOn(toast, 'error'); + it('On invalid post submission with empty body Error toast should be shown', async () => { + const toastSpy = vi.spyOn(toast, 'error'); renderStartPostModal(true, null); await wait(); @@ -134,7 +148,7 @@ describe('Testing StartPostModal Component: User Portal', () => { ); }); - test('On valid post submission Info toast should be shown', async () => { + it('On valid post submission Info toast should be shown', async () => { renderStartPostModal(true, null); await wait(); @@ -154,7 +168,7 @@ describe('Testing StartPostModal Component: User Portal', () => { // ); }); - test('If user image is null then default image should be shown', async () => { + it('If user image is null then default image should be shown', async () => { renderStartPostModal(true, null); await wait(); @@ -165,7 +179,7 @@ describe('Testing StartPostModal Component: User Portal', () => { ); }); - test('If user image is not null then user image should be shown', async () => { + it('If user image is not null then user image should be shown', async () => { renderStartPostModal(true, 'image.png'); await wait(); diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx b/src/components/UserPortal/UserNavbar/UserNavbar.spec.tsx similarity index 74% rename from src/components/UserPortal/UserNavbar/UserNavbar.test.tsx rename to src/components/UserPortal/UserNavbar/UserNavbar.spec.tsx index 8c3447f25a..6173871dbb 100644 --- a/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx +++ b/src/components/UserPortal/UserNavbar/UserNavbar.spec.tsx @@ -13,6 +13,22 @@ import UserNavbar from './UserNavbar'; import userEvent from '@testing-library/user-event'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; +/** + * Unit tests for UserNavbar component [User Portal]: + * + * 1. **Rendering UserNavbar**: Verifies that the `UserNavbar` component renders correctly. + * 2. **Switching language to English**: Ensures that clicking the language dropdown and selecting 'English' updates the language cookie to 'en'. + * 3. **Switching language to French**: Verifies that selecting 'French' updates the language cookie to 'fr'. + * 4. **Switching language to Hindi**: Confirms that choosing 'Hindi' updates the language cookie to 'hi'. + * 5. **Switching language to Spanish**: Ensures that selecting 'Spanish' sets the language cookie to 'sp'. + * 6. **Switching language to Chinese**: Verifies that selecting 'Chinese' changes the language cookie to 'zh'. + * 7. **Interacting with the dropdown menu**: Ensures the user can open the dropdown and see available options like 'Settings' and 'Logout'. + * 8. **Navigating to the 'Settings' page**: Confirms that clicking 'Settings' in the dropdown correctly navigates the user to the "/user/settings" page. + * + * The tests simulate interactions with the language dropdown and the user dropdown menu to ensure proper functionality of language switching and navigation. + * Mocked GraphQL mutation (`REVOKE_REFRESH_TOKEN`) and mock store are used to test the component in an isolated environment. + */ + async function wait(ms = 100): Promise { await act(() => { return new Promise((resolve) => { @@ -39,7 +55,7 @@ describe('Testing UserNavbar Component [User Portal]', () => { }); }); - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { render( @@ -55,7 +71,7 @@ describe('Testing UserNavbar Component [User Portal]', () => { await wait(); }); - test('The language is switched to English', async () => { + it('The language is switched to English', async () => { render( @@ -79,7 +95,7 @@ describe('Testing UserNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('en'); }); - test('The language is switched to fr', async () => { + it('The language is switched to fr', async () => { render( @@ -103,7 +119,7 @@ describe('Testing UserNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('fr'); }); - test('The language is switched to hi', async () => { + it('The language is switched to hi', async () => { render( @@ -127,7 +143,7 @@ describe('Testing UserNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('hi'); }); - test('The language is switched to sp', async () => { + it('The language is switched to sp', async () => { render( @@ -151,7 +167,7 @@ describe('Testing UserNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('sp'); }); - test('The language is switched to zh', async () => { + it('The language is switched to zh', async () => { render( @@ -175,7 +191,7 @@ describe('Testing UserNavbar Component [User Portal]', () => { expect(cookies.get('i18next')).toBe('zh'); }); - test('User can see and interact with the dropdown menu', async () => { + it('User can see and interact with the dropdown menu', async () => { render( @@ -195,7 +211,7 @@ describe('Testing UserNavbar Component [User Portal]', () => { expect(screen.getByTestId('logoutBtn')).toBeInTheDocument(); }); - test('User can navigate to the "Settings" page', async () => { + it('User can navigate to the "Settings" page', async () => { render( diff --git a/src/components/UserPortal/UserProfile/EventsAttendedByUser.test.tsx b/src/components/UserPortal/UserProfile/EventsAttendedByUser.spec.tsx similarity index 76% rename from src/components/UserPortal/UserProfile/EventsAttendedByUser.test.tsx rename to src/components/UserPortal/UserProfile/EventsAttendedByUser.spec.tsx index 82b173e399..dfdeb5523e 100644 --- a/src/components/UserPortal/UserProfile/EventsAttendedByUser.test.tsx +++ b/src/components/UserPortal/UserProfile/EventsAttendedByUser.spec.tsx @@ -4,6 +4,18 @@ import { EventsAttendedByUser } from './EventsAttendedByUser'; import { MockedProvider } from '@apollo/client/testing'; import { EVENT_DETAILS } from 'GraphQl/Queries/Queries'; +/** + * Unit tests for EventsAttendedByUser component: + * + * 1. **Rendering with events**: Verifies that the component renders properly when the user has attended events. + * - It checks for the presence of a title ('eventAttended') and ensures the correct number of event cards are rendered (2 events in this case). + * 2. **Rendering without events**: Ensures that when the user has not attended any events, a message indicating no events attended is displayed. + * - It checks for the presence of a 'noeventsAttended' message and ensures no event cards are rendered. + * + * Mock GraphQL queries (using `MockedProvider`) simulate the fetching of event details. + * The tests check the proper handling of different user event attendance scenarios. + */ + const mockT = (key: string, params?: Record): string => { if (params) { return Object.entries(params).reduce( @@ -96,7 +108,7 @@ describe('EventsAttendedByUser Component', () => { t: mockT, }; - test('renders the component with events', () => { + it('renders the component with events', () => { render( @@ -107,7 +119,7 @@ describe('EventsAttendedByUser Component', () => { expect(screen.getAllByTestId('usereventsCard')).toHaveLength(2); }); - test('renders no events message when user has no events', () => { + it('renders no events message when user has no events', () => { render( diff --git a/src/components/UserPortal/UserProfile/UserAddressFields.test.tsx b/src/components/UserPortal/UserProfile/UserAddressFields.spec.tsx similarity index 69% rename from src/components/UserPortal/UserProfile/UserAddressFields.test.tsx rename to src/components/UserPortal/UserProfile/UserAddressFields.spec.tsx index 7aff734bc6..9c0a6609e4 100644 --- a/src/components/UserPortal/UserProfile/UserAddressFields.test.tsx +++ b/src/components/UserPortal/UserProfile/UserAddressFields.spec.tsx @@ -2,12 +2,25 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { UserAddressFields } from './UserAddressFields'; import { countryOptions } from 'utils/formEnumFields'; +import { vi } from 'vitest'; + +/** + * Unit tests for UserAddressFields component: + * + * 1. **Rendering form fields**: Ensures address, state, and country fields are rendered. + * 2. **Displaying translated labels**: Verifies correct translations for labels. + * 3. **Handling input changes**: Tests if `handleFieldChange` is called with correct values for address, state, and country. + * 4. **Rendering country options**: Checks if all country options are displayed. + * 5. **Displaying initial values**: Ensures initial values (address, state, country) are correctly shown. + * + * `fireEvent` simulates user actions, and `vi.fn()` mocks callback functions. + */ describe('UserAddressFields', () => { const mockProps = { tCommon: (key: string) => `translated_${key}`, t: (key: string) => `translated_${key}`, - handleFieldChange: jest.fn(), + handleFieldChange: vi.fn(), userDetails: { address: '123 Test Street', state: 'Test State', @@ -16,10 +29,10 @@ describe('UserAddressFields', () => { }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('renders all form fields correctly', () => { + it('renders all form fields correctly', () => { render(); expect(screen.getByTestId('inputAddress')).toBeInTheDocument(); @@ -27,7 +40,7 @@ describe('UserAddressFields', () => { expect(screen.getByTestId('inputCountry')).toBeInTheDocument(); }); - test('displays correct labels with translations', () => { + it('displays correct labels with translations', () => { render(); expect(screen.getByText('translated_address')).toBeInTheDocument(); @@ -35,7 +48,7 @@ describe('UserAddressFields', () => { expect(screen.getByText('translated_country')).toBeInTheDocument(); }); - test('handles address input change', () => { + it('handles address input change', () => { render(); const addressInput = screen.getByTestId('inputAddress'); @@ -47,7 +60,7 @@ describe('UserAddressFields', () => { ); }); - test('handles state input change', () => { + it('handles state input change', () => { render(); const stateInput = screen.getByTestId('inputState'); @@ -59,7 +72,7 @@ describe('UserAddressFields', () => { ); }); - test('handles country selection change', () => { + it('handles country selection change', () => { render(); const countrySelect = screen.getByTestId('inputCountry'); @@ -68,7 +81,7 @@ describe('UserAddressFields', () => { expect(mockProps.handleFieldChange).toHaveBeenCalledWith('country', 'CA'); }); - test('renders all country options', () => { + it('renders all country options', () => { render(); const countrySelect = screen.getByTestId('inputCountry'); @@ -77,7 +90,7 @@ describe('UserAddressFields', () => { expect(options.length).toBe(countryOptions.length + 1); // +1 for disabled option }); - test('displays initial values correctly', () => { + it('displays initial values correctly', () => { render(); expect(screen.getByTestId('inputAddress')).toHaveValue('123 Test Street'); diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.spec.tsx similarity index 85% rename from src/components/UserPortal/UserSidebar/UserSidebar.test.tsx rename to src/components/UserPortal/UserSidebar/UserSidebar.spec.tsx index 79d603614f..6435353f89 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.spec.tsx @@ -15,6 +15,25 @@ import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import UserSidebar from './UserSidebar'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; + +/** + * Unit tests for UserSidebar component: + * + * 1. **Rendering with user data**: Verifies correct rendering when user data is fetched. + * 2. **Logo and title**: Ensures logo, title, and left drawer are visible. + * 3. **Empty organizations list**: Tests rendering when the user has no joined organizations. + * 4. **Organization image rendering**: Verifies rendering when organizations have an image. + * 5. **User profile and links**: Ensures user details and links like 'My Organizations' and 'Settings' are visible. + * 6. **Responsive rendering**: Tests correct rendering and drawer toggle on smaller screens. + * 7. **Active button style**: Verifies button style changes when clicked. + * 8. **Translation display**: Ensures translated text is shown. + * 9. **Sidebar closure on mobile**: Verifies sidebar closes when a link is clicked on mobile view. + * 10. **Drawer visibility on small screens**: Tests drawer visibility toggle based on `hideDrawer` prop. + * 11. **Drawer state change**: Verifies drawer visibility changes when `hideDrawer` prop changes. + * + * `fireEvent` simulates user actions, and `vi.fn()` mocks callback functions. + */ const { setItem } = useLocalStorage(); @@ -27,7 +46,7 @@ const resizeWindow = (width: number): void => { const props = { hideDrawer: true, - setHideDrawer: jest.fn(), + setHideDrawer: vi.fn(), }; const MOCKS = [ @@ -365,10 +384,10 @@ const renderUserSidebar = ( describe('UserSidebar Component Tests in User Portal', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('UserSidebar component renders correctly with user data present', async () => { + it('UserSidebar component renders correctly with user data present', async () => { await act(async () => { renderUserSidebar('properId', link); await wait(); @@ -376,7 +395,7 @@ describe('UserSidebar Component Tests in User Portal', () => { expect(screen.getByText('Talawa User Portal')).toBeInTheDocument(); }); - test('Displays the logo and title text of the User Portal', async () => { + it('Displays the logo and title text of the User Portal', async () => { await act(async () => { renderUserSidebar('properId', link); await wait(); @@ -385,7 +404,7 @@ describe('UserSidebar Component Tests in User Portal', () => { expect(screen.getByTestId('leftDrawerContainer')).toBeVisible(); }); - test('UserSidebar renders correctly when joinedOrganizations list is empty', async () => { + it('UserSidebar renders correctly when joinedOrganizations list is empty', async () => { await act(async () => { renderUserSidebar('orgEmpty', link); await wait(); @@ -393,7 +412,7 @@ describe('UserSidebar Component Tests in User Portal', () => { expect(screen.getByText('My Organizations')).toBeInTheDocument(); }); - test('Renders UserSidebar component with organization image when present', async () => { + it('Renders UserSidebar component with organization image when present', async () => { await act(async () => { renderUserSidebar('imagePresent', link); await wait(); @@ -401,19 +420,19 @@ describe('UserSidebar Component Tests in User Portal', () => { expect(screen.getByText('Settings')).toBeInTheDocument(); }); - test('User profile data renders with all expected navigation links visible', async () => { + it('User profile data renders with all expected navigation links visible', async () => { await act(async () => { renderUserSidebar('properId', link); await wait(); }); - const expectedLinks = ['My Organizations', 'Settings', 'Chat']; + const expectedLinks = ['My Organizations', 'Settings']; expectedLinks.forEach((link) => { expect(screen.getByText(link)).toBeInTheDocument(); }); }); - test('UserSidebar renders correctly on smaller screens and toggles drawer visibility', async () => { + it('UserSidebar renders correctly on smaller screens and toggles drawer visibility', async () => { await act(async () => { resizeWindow(800); render( @@ -433,7 +452,7 @@ describe('UserSidebar Component Tests in User Portal', () => { expect(props.setHideDrawer).toHaveBeenCalledWith(true); }); - test('Active route button style changes correctly upon click', async () => { + it('Active route button style changes correctly upon click', async () => { await act(async () => { renderUserSidebar('properId', link); await wait(); @@ -448,7 +467,7 @@ describe('UserSidebar Component Tests in User Portal', () => { expect(settingsBtn).toHaveClass('text-white btn btn-success'); }); - test('Translation hook displays expected text in UserSidebar', async () => { + it('Translation hook displays expected text in UserSidebar', async () => { await act(async () => { renderUserSidebar('properId', link); await wait(); @@ -458,23 +477,23 @@ describe('UserSidebar Component Tests in User Portal', () => { ).toBeInTheDocument(); }); - test('handleLinkClick function closes the sidebar on mobile view when a link is clicked', async () => { + it('handleLinkClick function closes the sidebar on mobile view when a link is clicked', async () => { resizeWindow(800); await act(async () => { renderUserSidebar('properId', link); await wait(); }); - const chatBtn = screen.getByTestId('chatBtn'); - fireEvent.click(chatBtn); + const settingsBtn = screen.getByTestId('settingsBtn'); + fireEvent.click(settingsBtn); expect(props.setHideDrawer).toHaveBeenCalledWith(true); }); describe('UserSidebar Drawer Visibility Tests on Smaller Screens', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('Clicking a link closes the drawer when window width is 820px or less', () => { + it('Clicking a link closes the drawer when window width is 820px or less', () => { act(() => { window.innerWidth = 820; window.dispatchEvent(new Event('resize')); @@ -499,7 +518,7 @@ describe('UserSidebar Component Tests in User Portal', () => { }); describe('UserSidebar Drawer State Tests', () => { - test('Drawer visibility changes based on hideDrawer prop', () => { + it('Drawer visibility changes based on hideDrawer prop', () => { const { rerender } = render( diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.tsx index 5e258f8a8e..010d8d0e52 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.tsx @@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'; import { NavLink } from 'react-router-dom'; import OrganizationsIcon from 'assets/svgs/organizations.svg?react'; import SettingsIcon from 'assets/svgs/settings.svg?react'; -import ChatIcon from 'assets/svgs/chat.svg?react'; import TalawaLogo from 'assets/svgs/talawa.svg?react'; import styles from './UserSidebar.module.css'; @@ -109,28 +108,6 @@ const userSidebar = ({ )} - - {({ isActive }) => ( - - )} - diff --git a/src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.test.tsx b/src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.spec.tsx similarity index 86% rename from src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.test.tsx rename to src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.spec.tsx index 2f28d9afd1..f26997f572 100644 --- a/src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.test.tsx +++ b/src/components/UserPortal/UserSidebarOrg/UserSidebarOrg.spec.tsx @@ -1,10 +1,8 @@ import React, { act } from 'react'; import { fireEvent, render, screen } 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 i18nForTest from 'utils/i18nForTest'; import type { InterfaceUserSidebarOrgProps } from './UserSidebarOrg'; import UserSidebarOrg from './UserSidebarOrg'; @@ -15,7 +13,24 @@ import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; +/** + * Unit tests for UserSidebarOrg component: + * + * 1. **Rendering with organization data**: Verifies correct rendering when data is fetched. + * 2. **Profile Page & Modal**: Ensures profile button and organization details modal appear. + * 3. **Menu Navigation**: Tests correct navigation when menu buttons like 'People' are clicked. + * 4. **Responsive Design**: Verifies sidebar behavior on screens. + * 5. **Organization Image**: Ensures correct rendering of organization image. + * 6. **Empty Organizations**: Verifies error message when no organizations exist. + * 7. **Drawer Visibility**: Tests drawer visibility with `hideDrawer` prop values. + * 8. **User Profile Rendering**: Confirms user details are displayed. + * 9. **Translation Display**: Ensures proper translation of UI text. + * 10. **Toast Notifications Mocking**: Mocks toast notifications during tests. + * + * `fireEvent` simulates user actions, and `vi.fn()` mocks callback functions. + */ const { setItem } = useLocalStorage(); const props: InterfaceUserSidebarOrgProps = { @@ -47,7 +62,7 @@ const props: InterfaceUserSidebarOrgProps = { }, ], hideDrawer: false, - setHideDrawer: jest.fn(), + setHideDrawer: vi.fn(), }; const MOCKS = [ @@ -213,11 +228,11 @@ const defaultScreens = [ 'All Organizations', ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); @@ -244,7 +259,7 @@ beforeEach(() => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); localStorage.clear(); }); @@ -253,7 +268,7 @@ const linkImage = new StaticMockLink(MOCKS_WITH_IMAGE, true); const linkEmpty = new StaticMockLink(MOCKS_EMPTY, true); describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { - test('Component should be rendered properly', async () => { + it('Component should be rendered properly', async () => { setItem('UserImage', ''); setItem('SuperAdmin', true); setItem('FirstName', 'John'); @@ -275,7 +290,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { }); }); - test('Testing Profile Page & Organization Detail Modal', async () => { + it('Testing Profile Page & Organization Detail Modal', async () => { setItem('UserImage', ''); setItem('SuperAdmin', true); setItem('FirstName', 'John'); @@ -295,7 +310,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { expect(screen.getByTestId(/orgBtn/i)).toBeInTheDocument(); }); - test('Testing Menu Buttons', async () => { + it('Testing Menu Buttons', async () => { setItem('UserImage', ''); setItem('SuperAdmin', true); setItem('FirstName', 'John'); @@ -316,7 +331,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { expect(global.window.location.pathname).toContain('/user/people/123'); }); - test('Testing when screen size is less than 820px', async () => { + it('Testing when screen size is less than 820px', async () => { setItem('SuperAdmin', true); render( @@ -339,7 +354,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { expect(window.location.pathname).toContain('user/people/123'); }); - test('Testing when image is present for Organization', async () => { + it('Testing when image is present for Organization', async () => { setItem('UserImage', ''); setItem('SuperAdmin', true); setItem('FirstName', 'John'); @@ -358,7 +373,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { await wait(); }); - test('Testing when Organization does not exists', async () => { + it('Testing when Organization does not exists', async () => { setItem('UserImage', ''); setItem('SuperAdmin', true); setItem('FirstName', 'John'); @@ -380,7 +395,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { ).toBeInTheDocument(); }); - test('Testing Drawer when hideDrawer is null', () => { + it('Testing Drawer when hideDrawer is null', () => { setItem('UserImage', ''); setItem('SuperAdmin', true); setItem('FirstName', 'John'); @@ -398,7 +413,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { ); }); - test('Testing Drawer when hideDrawer is true', () => { + it('Testing Drawer when hideDrawer is true', () => { setItem('UserImage', ''); setItem('SuperAdmin', true); setItem('FirstName', 'John'); diff --git a/src/components/UserProfileSettings/DeleteUser.test.tsx b/src/components/UserProfileSettings/DeleteUser.spec.tsx similarity index 90% rename from src/components/UserProfileSettings/DeleteUser.test.tsx rename to src/components/UserProfileSettings/DeleteUser.spec.tsx index 34ab44fbe5..d089d82607 100644 --- a/src/components/UserProfileSettings/DeleteUser.test.tsx +++ b/src/components/UserProfileSettings/DeleteUser.spec.tsx @@ -5,9 +5,10 @@ import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import DeleteUser from './DeleteUser'; +import { describe, it, expect } from 'vitest'; describe('Delete User component', () => { - test('renders delete user correctly', () => { + it('renders delete user correctly', () => { const { getByText, getAllByText } = render( diff --git a/src/components/UserProfileSettings/OtherSettings.test.tsx b/src/components/UserProfileSettings/OtherSettings.spec.tsx similarity index 89% rename from src/components/UserProfileSettings/OtherSettings.test.tsx rename to src/components/UserProfileSettings/OtherSettings.spec.tsx index 990a430931..490ad2322c 100644 --- a/src/components/UserProfileSettings/OtherSettings.test.tsx +++ b/src/components/UserProfileSettings/OtherSettings.spec.tsx @@ -5,9 +5,10 @@ import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import OtherSettings from './OtherSettings'; +import { describe, it, expect } from 'vitest'; describe('Delete User component', () => { - test('renders delete user correctly', () => { + it('renders delete user correctly', () => { const { getByText } = render( diff --git a/src/components/UserProfileSettings/UserProfile.test.tsx b/src/components/UserProfileSettings/UserProfile.spec.tsx similarity index 92% rename from src/components/UserProfileSettings/UserProfile.test.tsx rename to src/components/UserProfileSettings/UserProfile.spec.tsx index 82caad5d81..dbf7987789 100644 --- a/src/components/UserProfileSettings/UserProfile.test.tsx +++ b/src/components/UserProfileSettings/UserProfile.spec.tsx @@ -5,9 +5,10 @@ import { MockedProvider } from '@apollo/react-testing'; import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; +import { describe, it, expect } from 'vitest'; describe('UserProfile component', () => { - test('renders user profile details correctly', () => { + it('renders user profile details correctly', () => { const userDetails = { firstName: 'Christopher', lastName: 'Doe', diff --git a/src/components/UsersTableItem/UserTableItem.test.tsx b/src/components/UsersTableItem/UserTableItem.spec.tsx similarity index 98% rename from src/components/UsersTableItem/UserTableItem.test.tsx rename to src/components/UsersTableItem/UserTableItem.spec.tsx index 687165b78d..a0b0c39c86 100644 --- a/src/components/UsersTableItem/UserTableItem.test.tsx +++ b/src/components/UsersTableItem/UserTableItem.spec.tsx @@ -13,11 +13,9 @@ const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS2, true); const link3 = new StaticMockLink(MOCKS_UPDATE, true); import useLocalStorage from 'utils/useLocalstorage'; -import { - REMOVE_ADMIN_MUTATION, - REMOVE_MEMBER_MUTATION, -} from 'GraphQl/Mutations/mutations'; import userEvent from '@testing-library/user-event'; +import { vi } from 'vitest'; +import type * as RouterTypes from 'react-router-dom'; const { setItem } = useLocalStorage(); @@ -28,29 +26,34 @@ async function wait(ms = 100): Promise { }); }); } -const resetAndRefetchMock = jest.fn(); +const resetAndRefetchMock = vi.fn(); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), - warning: jest.fn(), + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), }, })); Object.defineProperty(window, 'location', { value: { - replace: jest.fn(), + replace: vi.fn(), }, writable: true, }); -const mockNavgatePush = jest.fn(); +const mockNavgatePush = vi.fn(); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useNavigate: () => mockNavgatePush, -})); +vi.mock('react-router-dom', async () => { + const actual = (await vi.importActual( + 'react-router-dom', + )) as typeof RouterTypes; + return { + ...actual, + useNavigate: () => mockNavgatePush, + }; +}); beforeEach(() => { setItem('SuperAdmin', true); @@ -59,11 +62,11 @@ beforeEach(() => { afterEach(() => { localStorage.clear(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('Testing User Table Item', () => { - console.error = jest.fn((message) => { + console.error = vi.fn((message) => { if (message.includes('validateDOMNesting')) { return; } @@ -1183,25 +1186,6 @@ describe('Testing User Table Item', () => { resetAndRefetch: resetAndRefetchMock, }; - const mocks = [ - { - request: { - query: REMOVE_MEMBER_MUTATION, - variables: { - userId: '123', - orgId: 'xyz', - }, - }, - result: { - errors: [ - { - message: 'User does not exist', - }, - ], - }, - }, - ]; - render( diff --git a/src/components/Venues/VenueCard.tsx b/src/components/Venues/VenueCard.tsx index 752ac95139..43da734bd6 100644 --- a/src/components/Venues/VenueCard.tsx +++ b/src/components/Venues/VenueCard.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Card, Button } from 'react-bootstrap'; import defaultImg from 'assets/images/defaultImg.png'; import PeopleIcon from 'assets/svgs/people.svg?react'; -import styles from 'screens/OrganizationVenues/OrganizationVenues.module.css'; +import styles from '../../style/app.module.css'; import { useTranslation } from 'react-i18next'; import type { InterfaceQueryVenueListItem } from 'utils/interfaces'; diff --git a/src/components/Venues/VenueModal.test.tsx b/src/components/Venues/VenueModal.spec.tsx similarity index 93% rename from src/components/Venues/VenueModal.test.tsx rename to src/components/Venues/VenueModal.spec.tsx index b299c8ff20..45560c40cb 100644 --- a/src/components/Venues/VenueModal.test.tsx +++ b/src/components/Venues/VenueModal.spec.tsx @@ -4,7 +4,6 @@ import type { RenderResult } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import type { InterfaceVenueModalProps } from './VenueModal'; @@ -19,6 +18,8 @@ import { UPDATE_VENUE_MUTATION, } from 'GraphQl/Mutations/mutations'; import type { ApolloLink } from '@apollo/client'; +import { vi } from 'vitest'; +import type * as RouterTypes from 'react-router-dom'; const MOCKS = [ { @@ -65,10 +66,16 @@ const MOCKS = [ const link = new StaticMockLink(MOCKS, true); const mockId = 'orgId'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockId }), -})); + +vi.mock('react-router-dom', async () => { + const actual = (await vi.importActual( + 'react-router-dom', + )) as typeof RouterTypes; + return { + ...actual, + useParams: () => ({ orgId: mockId }), + }; +}); async function wait(ms = 100): Promise { await act(() => { @@ -78,26 +85,26 @@ async function wait(ms = 100): Promise { }); } -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warning: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warning: vi.fn(), + error: vi.fn(), }, })); const props: InterfaceVenueModalProps[] = [ { show: true, - onHide: jest.fn(), + onHide: vi.fn(), edit: false, venueData: null, - refetchVenues: jest.fn(), + refetchVenues: vi.fn(), orgId: 'orgId', }, { show: true, - onHide: jest.fn(), + onHide: vi.fn(), edit: true, venueData: { _id: 'venue1', @@ -106,7 +113,7 @@ const props: InterfaceVenueModalProps[] = [ image: 'image1', capacity: '100', }, - refetchVenues: jest.fn(), + refetchVenues: vi.fn(), orgId: 'orgId', }, ]; @@ -129,7 +136,7 @@ const renderVenueModal = ( }; describe('VenueModal', () => { - global.alert = jest.fn(); + global.alert = vi.fn(); test('renders correctly when show is true', async () => { renderVenueModal(props[0], link); diff --git a/src/screens/BlockUser/BlockUser.test.tsx b/src/screens/BlockUser/BlockUser.spec.tsx similarity index 52% rename from src/screens/BlockUser/BlockUser.test.tsx rename to src/screens/BlockUser/BlockUser.spec.tsx index c851470d9b..51d16a61f9 100644 --- a/src/screens/BlockUser/BlockUser.test.tsx +++ b/src/screens/BlockUser/BlockUser.spec.tsx @@ -1,4 +1,4 @@ -import React, { act } from 'react'; +import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -10,16 +10,15 @@ import { BLOCK_PAGE_MEMBER_LIST, ORGANIZATIONS_LIST, } from 'GraphQl/Queries/Queries'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import { ToastContainer } from 'react-toastify'; import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; import BlockUser from './BlockUser'; +import { vi, describe, beforeEach, test, expect } from 'vitest'; let userQueryCalled = false; @@ -241,94 +240,22 @@ const MOCKS = [ }, }, ]; -const MOCKS_EMPTY = [ - { - request: { - query: ORGANIZATIONS_LIST, - variables: { - id: 'orgid', - }, - }, - result: { - data: { - organizations: [ - { - _id: 'orgid', - image: '', - creator: { - firstName: 'firstName', - lastName: 'lastName', - email: 'email', - }, - name: 'name', - description: 'description', - location: 'location', - members: { - _id: 'id', - firstName: 'firstName', - lastName: 'lastName', - email: 'email', - }, - admins: { - _id: 'id', - firstName: 'firstName', - lastName: 'lastName', - email: 'email', - }, - membershipRequests: { - _id: 'id', - user: { - firstName: 'firstName', - lastName: 'lastName', - email: 'email', - }, - }, - blockedUsers: { - _id: 'id', - firstName: 'firstName', - lastName: 'lastName', - email: 'email', - }, - }, - ], - }, - }, - }, - - { - request: { - query: BLOCK_PAGE_MEMBER_LIST, - variables: { - firstName_contains: 'Peter', - lastName_contains: '', - orgId: 'orgid', - }, - }, - result: { - data: { - organizationsMemberConnection: { - edges: [], - }, - }, - }, - }, -]; const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(MOCKS_EMPTY, true); async function wait(ms = 500): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); + await new Promise((resolve) => { + setTimeout(resolve, ms); }); } -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgid' }), -})); +vi.mock('react-router-dom', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + useParams: () => ({ orgId: 'orgid' }), + }; +}); describe('Testing Block/Unblock user screen', () => { beforeEach(() => { @@ -336,7 +263,7 @@ describe('Testing Block/Unblock user screen', () => { }); test('Components should be rendered properly', async () => { - window.location.assign('/blockuser/orgid'); + window.history.pushState({}, 'Test page', '/blockuser/orgid'); render( @@ -354,11 +281,11 @@ describe('Testing Block/Unblock user screen', () => { expect(screen.getByText('Search By First Name')).toBeInTheDocument(); - expect(window.location).toBeAt('/blockuser/orgid'); + expect(window.location.pathname).toBe('/blockuser/orgid'); }); test('Testing block user functionality', async () => { - window.location.assign('/blockuser/orgid'); + window.history.pushState({}, 'Test page', '/blockuser/orgid'); render( @@ -372,9 +299,7 @@ describe('Testing Block/Unblock user screen', () => { , ); - await act(async () => { - userEvent.click(screen.getByTestId('userFilter')); - }); + userEvent.click(screen.getByTestId('userFilter')); userEvent.click(screen.getByTestId('showMembers')); await wait(); @@ -387,11 +312,11 @@ describe('Testing Block/Unblock user screen', () => { expect(screen.getByTestId('blockUser123')).toBeInTheDocument(); expect(screen.getByTestId('unBlockUser456')).toBeInTheDocument(); - expect(window.location).toBeAt('/blockuser/orgid'); + expect(window.location.pathname).toBe('/blockuser/orgid'); }); test('Testing unblock user functionality', async () => { - window.location.assign('/blockuser/orgid'); + window.history.pushState({}, 'Test page', '/blockuser/orgid'); render( @@ -404,9 +329,7 @@ describe('Testing Block/Unblock user screen', () => { , ); - await act(async () => { - userEvent.click(screen.getByTestId('userFilter')); - }); + userEvent.click(screen.getByTestId('userFilter')); userEvent.click(screen.getByTestId('showMembers')); await wait(); @@ -420,11 +343,11 @@ describe('Testing Block/Unblock user screen', () => { expect(screen.getByTestId('blockUser123')).toBeInTheDocument(); expect(screen.getByTestId('unBlockUser456')).toBeInTheDocument(); - expect(window.location).toBeAt('/blockuser/orgid'); + expect(window.location.pathname).toBe('/blockuser/orgid'); }); test('Testing First Name Filter', async () => { - window.location.assign('/blockuser/orgid'); + window.history.pushState({}, 'Test page', '/blockuser/orgid'); render( @@ -437,9 +360,7 @@ describe('Testing Block/Unblock user screen', () => { , ); - await act(async () => { - userEvent.click(screen.getByTestId('userFilter')); - }); + userEvent.click(screen.getByTestId('userFilter')); userEvent.click(screen.getByTestId('showMembers')); await wait(); @@ -448,9 +369,7 @@ describe('Testing Block/Unblock user screen', () => { expect(screen.getByText('Sam Smith')).toBeInTheDocument(); // Open Dropdown - await act(async () => { - userEvent.click(screen.getByTestId('nameFilter')); - }); + userEvent.click(screen.getByTestId('nameFilter')); // Select option and enter first name userEvent.click(screen.getByTestId('searchByFirstName')); const firstNameInput = screen.getByPlaceholderText(/Search by First Name/i); @@ -461,11 +380,11 @@ describe('Testing Block/Unblock user screen', () => { expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.queryByText('Sam Smith')).not.toBeInTheDocument(); - expect(window.location).toBeAt('/blockuser/orgid'); + expect(window.location.pathname).toBe('/blockuser/orgid'); }); test('Testing Last Name Filter', async () => { - window.location.assign('/blockuser/orgid'); + window.history.pushState({}, 'Test page', '/blockuser/orgid'); render( @@ -479,178 +398,6 @@ describe('Testing Block/Unblock user screen', () => { , ); - await act(async () => { - userEvent.click(screen.getByTestId('userFilter')); - }); - userEvent.click(screen.getByTestId('showMembers')); - - await wait(); - - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.getByText('Sam Smith')).toBeInTheDocument(); - - // Open Dropdown - await act(async () => { - userEvent.click(screen.getByTestId('nameFilter')); - }); - // Select option and enter last name - userEvent.click(screen.getByTestId('searchByLastName')); - const lastNameInput = screen.getByPlaceholderText(/Search by Last Name/i); - userEvent.type(lastNameInput, 'doe{enter}'); - - await wait(700); - - expect(lastNameInput).toHaveValue('doe'); - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.queryByText('Sam Smith')).not.toBeInTheDocument(); - expect(window.location).toBeAt('/blockuser/orgid'); - }); - - test('Testing No Spammers Present', async () => { - window.location.assign('/blockuser/orgid'); - render( - - - - - - - - - , - ); - - await wait(); - expect(screen.getByText(/No spammer found/i)).toBeInTheDocument(); - expect(window.location).toBeAt('/blockuser/orgid'); - }); - - test('Testing All Members', async () => { - window.location.assign('/blockuser/orgid'); - - render( - - - - - - - - - - , - ); - await wait(); - await act(async () => { - userEvent.click(screen.getByTestId('userFilter')); - }); - userEvent.click(screen.getByTestId('showMembers')); - - await wait(700); - - expect(screen.getByTestId(/userFilter/i)).toHaveTextContent('All Members'); - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.getByText('Sam Smith')).toBeInTheDocument(); - - expect(window.location).toBeAt('/blockuser/orgid'); - }); - - test('Testing Blocked Users', async () => { - window.location.assign('/blockuser/orgid'); - - render( - - - - - - - - - - , - ); - - await act(async () => { - userEvent.click(screen.getByTestId('userFilter')); - }); - - userEvent.click(screen.getByTestId('showBlockedMembers')); - await wait(); - - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.queryByText('Sam Smith')).not.toBeInTheDocument(); - - expect(window.location).toBeAt('/blockuser/orgid'); - }); - - test('Testing table data getting rendered', async () => { - window.location.assign('/orglist/orgid'); - const link = new StaticMockLink(MOCKS, true); - render( - - - - - - - - - , - ); - await act(async () => { - userEvent.click(screen.getByTestId('userFilter')); - }); - userEvent.click(screen.getByTestId('showMembers')); - - await wait(); - - expect(screen.getByTestId(/userList/)).toBeInTheDocument(); - expect(screen.getAllByText('Block/Unblock')).toHaveLength(1); - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.getByText('Sam Smith')).toBeInTheDocument(); - }); - - test('Testing No Results Found', async () => { - window.location.assign('/blockuser/orgid'); - render( - - - - - - - - - , - ); - - const input = screen.getByPlaceholderText('Search By First Name'); - await act(async () => { - userEvent.type(input, 'Peter{enter}'); - }); - await wait(700); - expect( - screen.getByText(`No results found for "Peter"`), - ).toBeInTheDocument(); - expect(window.location).toBeAt('/blockuser/orgid'); - }); - - test('Testing Search functionality', async () => { - window.location.assign('/blockuser/orgid'); - - render( - - - - - - - - - - , - ); await wait(); const searchBar = screen.getByTestId(/searchByName/i); const searchBtn = screen.getByTestId(/searchBtn/i); diff --git a/src/screens/CommunityProfile/CommunityProfile.module.css b/src/screens/CommunityProfile/CommunityProfile.module.css deleted file mode 100644 index 1e6eac2bae..0000000000 --- a/src/screens/CommunityProfile/CommunityProfile.module.css +++ /dev/null @@ -1,41 +0,0 @@ -.card { - width: fit-content; -} - -.cardHeader { - padding: 1.25rem 1rem 1rem 1rem; - border-bottom: 1px solid var(--bs-gray-200); - display: flex; - justify-content: space-between; - align-items: center; -} - -.cardHeader .cardTitle { - font-size: 1.5rem; -} - -.formLabel { - font-weight: normal; - padding-bottom: 0; - font-size: 1rem; - color: black; -} -.cardBody { - min-height: 180px; -} - -.cardBody .textBox { - margin: 0 0 3rem 0; - color: var(--bs-secondary); -} - -.socialInput { - height: 2.5rem; -} - -@media (max-width: 520px) { - .btn { - flex-direction: column; - justify-content: center; - } -} diff --git a/src/screens/CommunityProfile/CommunityProfile.tsx b/src/screens/CommunityProfile/CommunityProfile.tsx index d96c923eb3..05f328ced0 100644 --- a/src/screens/CommunityProfile/CommunityProfile.tsx +++ b/src/screens/CommunityProfile/CommunityProfile.tsx @@ -18,7 +18,7 @@ import { SlackLogo, } from 'assets/svgs/social-icons'; import convertToBase64 from 'utils/convertToBase64'; -import styles from './CommunityProfile.module.css'; +import styles from '../../style/app.module.css'; import { errorHandler } from 'utils/errorHandler'; import UpdateSession from '../../components/UpdateSession/UpdateSession'; @@ -90,7 +90,7 @@ const CommunityProfile = (): JSX.Element => { React.useEffect(() => { const preLoginData: PreLoginImageryDataType | undefined = data?.getCommunityData; - preLoginData && + if (preLoginData) { setProfileVariable({ name: preLoginData.name ?? '', websiteLink: preLoginData.websiteLink ?? '', @@ -104,6 +104,7 @@ const CommunityProfile = (): JSX.Element => { reddit: preLoginData.socialMediaUrls.reddit ?? '', slack: preLoginData.socialMediaUrls.slack ?? '', }); + } }, [data]); /** diff --git a/src/screens/EventManagement/EventManagement.tsx b/src/screens/EventManagement/EventManagement.tsx index 2e5cdbd419..e355ac1acc 100644 --- a/src/screens/EventManagement/EventManagement.tsx +++ b/src/screens/EventManagement/EventManagement.tsx @@ -17,6 +17,7 @@ import VolunteerContainer from 'screens/EventVolunteers/VolunteerContainer'; import EventAgendaItems from 'components/EventManagement/EventAgendaItems/EventAgendaItems'; import useLocalStorage from 'utils/useLocalstorage'; import EventAttendance from 'components/EventManagement/EventAttendance/EventAttendance'; +import EventRegistrants from 'components/EventManagement/EventRegistrant/EventRegistrants'; /** * List of tabs for the event dashboard. * @@ -231,7 +232,9 @@ const EventManagement = (): JSX.Element => { ); case 'registrants': return ( -
Event Registrants
+
+ +
); case 'attendance': return ( diff --git a/src/screens/EventVolunteers/EventVolunteers.module.css b/src/screens/EventVolunteers/EventVolunteers.module.css deleted file mode 100644 index 84b19f0a9f..0000000000 --- a/src/screens/EventVolunteers/EventVolunteers.module.css +++ /dev/null @@ -1,266 +0,0 @@ -/* Toggle Btn */ -.toggleGroup { - width: 50%; - min-width: 20rem; - margin: 0.5rem 0rem; -} - -.toggleBtn { - padding: 0rem; - height: 2rem; - display: flex; - justify-content: center; - align-items: center; -} - -.toggleBtn:hover { - color: #31bb6b !important; -} - -input[type='radio']:checked + label { - background-color: #31bb6a50 !important; -} - -input[type='radio']:checked + label:hover { - color: black !important; -} - -.actionItemsContainer { - height: 90vh; -} - -.actionItemModal { - max-width: 80vw; - margin-top: 2vh; - margin-left: 13vw; -} - -.datediv { - display: flex; - flex-direction: row; -} - -.datebox { - width: 90%; - border-radius: 7px; - outline: none; - box-shadow: none; - padding-top: 2px; - padding-bottom: 2px; - padding-right: 5px; - padding-left: 5px; - margin-right: 5px; - margin-left: 5px; -} - -.dropdownToggle { - margin-bottom: 0; - display: flex; -} - -.dropdownModalToggle { - width: 50%; -} - -.errorIcon { - transform: scale(1.5); - color: var(--bs-danger); - margin-bottom: 1rem; -} - -.greenregbtn { - margin: 1rem 0 0; - margin-top: 15px; - border: 1px solid var(--bs-gray-300); - box-shadow: 0 2px 2px var(--bs-gray-300); - padding: 10px 10px; - border-radius: 5px; - background-color: var(--bs-primary); - width: 100%; - font-size: 16px; - color: var(--bs-white); - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; - width: 100%; -} - -hr { - border: none; - height: 1px; - background-color: var(--bs-gray-500); - margin: 1rem; -} - -.iconContainer { - display: flex; - justify-content: flex-end; -} -.icon { - margin: 1px; -} - -.message { - margin-top: 25%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.preview { - display: flex; - flex-direction: row; - font-weight: 900; - font-size: 16px; - color: rgb(80, 80, 80); -} - -.removeFilterIcon { - cursor: pointer; -} - -.searchForm { - display: inline; -} - -.view { - margin-left: 2%; - font-weight: 600; - font-size: 16px; - color: var(--bs-gray-600); -} - -/* header (search, filter, dropdown) */ -.btnsContainer { - display: flex; - margin: 0.5rem 0 1.5rem 0; -} - -.btnsContainer .input { - flex: 1; - min-width: 18rem; - position: relative; -} - -.btnsContainer input { - outline: 1px solid var(--bs-gray-400); -} - -.btnsContainer .input button { - width: 52px; -} - -.noOutline input { - outline: none; -} - -.noOutline input:disabled { - -webkit-text-fill-color: black !important; -} - -.noOutline textarea:disabled { - -webkit-text-fill-color: black !important; -} - -.inputField { - margin-top: 10px; - margin-bottom: 10px; - background-color: white; - box-shadow: 0 1px 1px #31bb6b; -} - -.inputField > button { - padding-top: 10px; - padding-bottom: 10px; -} - -.dropdown { - background-color: white; - border: 1px solid #31bb6b; - position: relative; - display: inline-block; - color: #31bb6b; -} - -/* Action Items Data Grid */ -.rowBackground { - background-color: var(--bs-white); - max-height: 120px; -} - -.tableHeader { - background-color: var(--bs-primary); - color: var(--bs-white); - font-size: 1rem; -} - -.chipIcon { - height: 0.9rem !important; -} - -.chip { - height: 1.5rem !important; -} - -.active { - background-color: #31bb6a50 !important; -} - -.pending { - background-color: #ffd76950 !important; - color: #bb952bd0 !important; - border-color: #bb952bd0 !important; -} - -/* Modals */ -.itemModal { - max-width: 80vw; - margin-top: 2vh; - margin-left: 13vw; -} - -.titlemodal { - color: #707070; - font-weight: 600; - font-size: 32px; - width: 65%; - margin-bottom: 0px; -} - -.modalCloseBtn { - width: 40px; - height: 40px; - padding: 1rem; - display: flex; - justify-content: center; - align-items: center; -} - -.imageContainer { - display: flex; - align-items: center; - justify-content: center; - margin-right: 0.5rem; -} - -.TableImage { - object-fit: cover; - width: 25px !important; - height: 25px !important; - border-radius: 100% !important; -} - -.avatarContainer { - width: 28px; - height: 26px; -} - -/* Modal Table (Groups & Assignments) */ -.modalTable { - max-height: 220px; - overflow-y: auto; -} diff --git a/src/screens/EventVolunteers/Requests/Requests.tsx b/src/screens/EventVolunteers/Requests/Requests.tsx index 41abcad763..b19be3d2a0 100644 --- a/src/screens/EventVolunteers/Requests/Requests.tsx +++ b/src/screens/EventVolunteers/Requests/Requests.tsx @@ -13,7 +13,7 @@ import { type GridColDef, } from '@mui/x-data-grid'; import Avatar from 'components/Avatar/Avatar'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import { USER_VOLUNTEER_MEMBERSHIP } from 'GraphQl/Queries/EventVolunteerQueries'; import type { InterfaceVolunteerMembership } from 'utils/interfaces'; import dayjs from 'dayjs'; @@ -154,7 +154,7 @@ function requests(): JSX.Element { align: 'center', headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return params.row.id; }, @@ -167,7 +167,7 @@ function requests(): JSX.Element { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { const { firstName, lastName, image } = params.row.volunteer.user; return ( @@ -180,14 +180,14 @@ function requests(): JSX.Element { src={image} alt="volunteer" data-testid={`volunteer_image`} - className={styles.TableImage} + className={styles.TableImages} /> ) : (
@@ -205,7 +205,7 @@ function requests(): JSX.Element { minWidth: 150, align: 'center', headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, sortable: false, renderCell: (params: GridCellParams) => { return dayjs(params.row.createdAt).format('DD/MM/YYYY'); @@ -219,7 +219,7 @@ function requests(): JSX.Element { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return ( <> @@ -251,7 +251,7 @@ function requests(): JSX.Element { return (
{/* Header with search, filter and Create Button */} -
+
{ setSearchValue(e.target.value); @@ -282,7 +282,7 @@ function requests(): JSX.Element { @@ -316,7 +316,7 @@ function requests(): JSX.Element { hideFooter={true} getRowId={(row) => row._id} sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} + getRowClassName={() => `${styles.rowBackgrounds}`} autoHeight rowHeight={65} rows={requests.map((request, index) => ({ diff --git a/src/screens/EventVolunteers/VolunteerContainer.test.tsx b/src/screens/EventVolunteers/VolunteerContainer.spec.tsx similarity index 71% rename from src/screens/EventVolunteers/VolunteerContainer.test.tsx rename to src/screens/EventVolunteers/VolunteerContainer.spec.tsx index 928d04195c..292dcd9b34 100644 --- a/src/screens/EventVolunteers/VolunteerContainer.test.tsx +++ b/src/screens/EventVolunteers/VolunteerContainer.spec.tsx @@ -12,9 +12,28 @@ import userEvent from '@testing-library/user-event'; import { MOCKS } from './Volunteers/Volunteers.mocks'; import { StaticMockLink } from 'utils/StaticMockLink'; import { LocalizationProvider } from '@mui/x-date-pickers'; +import { describe, it, beforeEach, expect, vi } from 'vitest'; + +/** + * Unit tests for the `VolunteerContainer` component. + * + * The tests ensure the `VolunteerContainer` component renders correctly with various routes and URL parameters. + * Mocked dependencies are used to isolate the component and verify its behavior. + * All tests are covered. + */ const link1 = new StaticMockLink(MOCKS); +const mockedUseParams = vi.fn(); + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => mockedUseParams(), + }; +}); + const renderVolunteerContainer = (): RenderResult => { return render( @@ -41,18 +60,21 @@ const renderVolunteerContainer = (): RenderResult => { }; describe('Testing Volunteer Container', () => { - beforeAll(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId', eventId: 'eventId' }), - })); + beforeEach(() => { + vi.clearAllMocks(); }); - afterAll(() => { - jest.clearAllMocks(); + it('should redirect to fallback URL if URL params are undefined', async () => { + mockedUseParams.mockReturnValue({}); + + renderVolunteerContainer(); + + await waitFor(() => { + expect(screen.getByTestId('paramsError')).toBeInTheDocument(); + }); }); - it('should redirect to fallback URL if URL params are undefined', async () => { + it('Testing Volunteer Container Screen -> Toggle screens', async () => { render( @@ -71,24 +93,26 @@ describe('Testing Volunteer Container', () => { , ); - await waitFor(() => { - expect(screen.getByTestId('paramsError')).toBeInTheDocument(); - }); - }); + mockedUseParams.mockReturnValue({ orgId: 'orgId', eventId: 'eventId' }); - test('Testing Volunteer Container Screen -> Toggle screens', async () => { renderVolunteerContainer(); const groupRadio = await screen.findByTestId('groupsRadio'); - expect(groupRadio).toBeInTheDocument(); - userEvent.click(groupRadio); - const requestsRadio = await screen.findByTestId('requestsRadio'); - expect(requestsRadio).toBeInTheDocument(); - userEvent.click(requestsRadio); - const individualRadio = await screen.findByTestId('individualRadio'); + + expect(groupRadio).toBeInTheDocument(); + expect(requestsRadio).toBeInTheDocument(); expect(individualRadio).toBeInTheDocument(); - userEvent.click(individualRadio); + + await waitFor(async () => { + await userEvent.click(groupRadio); + await userEvent.click(requestsRadio); + await userEvent.click(individualRadio); + }); + + await waitFor(() => { + expect(screen.getByTestId('paramsError')).toBeInTheDocument(); + }); }); }); diff --git a/src/screens/EventVolunteers/VolunteerContainer.tsx b/src/screens/EventVolunteers/VolunteerContainer.tsx index 1a425a706e..7e28124b43 100644 --- a/src/screens/EventVolunteers/VolunteerContainer.tsx +++ b/src/screens/EventVolunteers/VolunteerContainer.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Navigate, useParams } from 'react-router-dom'; -import styles from './EventVolunteers.module.css'; +import styles from '../../style/app.module.css'; import { HiUserGroup, HiUser } from 'react-icons/hi2'; import Volunteers from './Volunteers/Volunteers'; import VolunteerGroups from './VolunteerGroups/VolunteerGroups'; @@ -34,7 +34,7 @@ function volunteerContainer(): JSX.Element { return (
- + {t( `${dataType === 'group' ? 'volunteerGroups' : dataType === 'individual' ? 'volunteers' : 'requests'}`, )} diff --git a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.test.tsx b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.spec.tsx similarity index 92% rename from src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.test.tsx rename to src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.spec.tsx index 05c2dab5ff..8e726028db 100644 --- a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.test.tsx +++ b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.spec.tsx @@ -16,11 +16,18 @@ import { toast } from 'react-toastify'; import type { InterfaceDeleteVolunteerGroupModal } from './VolunteerGroupDeleteModal'; import VolunteerGroupDeleteModal from './VolunteerGroupDeleteModal'; import userEvent from '@testing-library/user-event'; +import { vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +/** + * Mock implementation of the `react-toastify` module. + * Mocks the `toast` object with `success` and `error` methods to allow testing + * without triggering actual toast notifications. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -39,8 +46,8 @@ const t = { const itemProps: InterfaceDeleteVolunteerGroupModal[] = [ { isOpen: true, - hide: jest.fn(), - refetchGroups: jest.fn(), + hide: vi.fn(), + refetchGroups: vi.fn(), group: { _id: 'groupId', name: 'Group 1', diff --git a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.tsx b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.tsx index 33132bfd33..89c788e220 100644 --- a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.tsx +++ b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupDeleteModal.tsx @@ -1,5 +1,5 @@ import { Button, Modal } from 'react-bootstrap'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useMutation } from '@apollo/client'; diff --git a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.test.tsx b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.spec.tsx similarity index 96% rename from src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.test.tsx rename to src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.spec.tsx index 2fc0b2e348..79b1d94545 100644 --- a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.test.tsx +++ b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.spec.tsx @@ -22,11 +22,18 @@ import { toast } from 'react-toastify'; import type { InterfaceVolunteerGroupModal } from './VolunteerGroupModal'; import GroupModal from './VolunteerGroupModal'; import userEvent from '@testing-library/user-event'; +import { vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +/** + * Mock implementation of the `react-toastify` module. + * Mocks the `toast` object with `success` and `error` methods to allow testing + * without triggering actual toast notifications. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -45,19 +52,19 @@ const t = { const itemProps: InterfaceVolunteerGroupModal[] = [ { isOpen: true, - hide: jest.fn(), + hide: vi.fn(), eventId: 'eventId', orgId: 'orgId', - refetchGroups: jest.fn(), + refetchGroups: vi.fn(), mode: 'create', group: null, }, { isOpen: true, - hide: jest.fn(), + hide: vi.fn(), eventId: 'eventId', orgId: 'orgId', - refetchGroups: jest.fn(), + refetchGroups: vi.fn(), mode: 'edit', group: { _id: 'groupId', @@ -96,10 +103,10 @@ const itemProps: InterfaceVolunteerGroupModal[] = [ }, { isOpen: true, - hide: jest.fn(), + hide: vi.fn(), eventId: 'eventId', orgId: 'orgId', - refetchGroups: jest.fn(), + refetchGroups: vi.fn(), mode: 'edit', group: { _id: 'groupId', diff --git a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.tsx b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.tsx index 5bfb1eff2b..e36ecaa0bd 100644 --- a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.tsx +++ b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupModal.tsx @@ -5,7 +5,7 @@ import type { InterfaceUserInfo, InterfaceVolunteerGroupInfo, } from 'utils/interfaces'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useMutation, useQuery } from '@apollo/client'; diff --git a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.test.tsx b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.spec.tsx similarity index 98% rename from src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.test.tsx rename to src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.spec.tsx index 94c34923a2..b029909809 100644 --- a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.test.tsx +++ b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.spec.tsx @@ -11,6 +11,7 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import i18n from 'utils/i18nForTest'; import type { InterfaceVolunteerGroupViewModal } from './VolunteerGroupViewModal'; import VolunteerGroupViewModal from './VolunteerGroupViewModal'; +import { vi } from 'vitest'; const t = { ...JSON.parse( @@ -25,7 +26,7 @@ const t = { const itemProps: InterfaceVolunteerGroupViewModal[] = [ { isOpen: true, - hide: jest.fn(), + hide: vi.fn(), group: { _id: 'groupId', name: 'Group 1', @@ -63,7 +64,7 @@ const itemProps: InterfaceVolunteerGroupViewModal[] = [ }, { isOpen: true, - hide: jest.fn(), + hide: vi.fn(), group: { _id: 'groupId', name: 'Group 1', diff --git a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.tsx b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.tsx index 70994bd4e5..5fb090649f 100644 --- a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.tsx +++ b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroupViewModal.tsx @@ -1,6 +1,6 @@ import { Button, Form, Modal } from 'react-bootstrap'; import type { InterfaceVolunteerGroupInfo } from 'utils/interfaces'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -123,14 +123,14 @@ const VolunteerGroupViewModal: React.FC = ({ src={leader.image} alt="Volunteer" data-testid="leader_image" - className={styles.TableImage} + className={styles.TableImages} /> ) : (
= ({ src={creator.image} alt="Volunteer" data-testid="creator_image" - className={styles.TableImage} + className={styles.TableImages} /> ) : (
{ ); }; +/** Mock useParams to provide consistent test data */ + describe('Testing VolunteerGroups Screen', () => { beforeAll(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId', eventId: 'eventId' }), - })); + vi.mock('react-router-dom', async () => { + const actualDom = await vi.importActual('react-router-dom'); + return { + ...actualDom, + useParams: vi.fn(), + }; + }); }); afterAll(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); + const mockRouteParams = (orgId = 'orgId', eventId = 'eventId'): void => { + vi.mocked(useParams).mockReturnValue({ orgId, eventId }); + }; + it('should redirect to fallback URL if URL params are undefined', async () => { + /** Mocking the useParams hook to return undefined parameters */ + mockRouteParams('', ''); render( @@ -98,12 +110,14 @@ describe('Testing VolunteerGroups Screen', () => { }); it('should render Groups screen', async () => { + mockRouteParams(); renderVolunteerGroups(link1); const searchInput = await screen.findByTestId('searchBy'); expect(searchInput).toBeInTheDocument(); }); it('Check Sorting Functionality', async () => { + mockRouteParams(); renderVolunteerGroups(link1); const searchInput = await screen.findByTestId('searchBy'); expect(searchInput).toBeInTheDocument(); @@ -133,6 +147,7 @@ describe('Testing VolunteerGroups Screen', () => { }); it('Search by Groups', async () => { + mockRouteParams(); renderVolunteerGroups(link1); const searchInput = await screen.findByTestId('searchBy'); expect(searchInput).toBeInTheDocument(); @@ -153,6 +168,7 @@ describe('Testing VolunteerGroups Screen', () => { }); it('Search by Leader', async () => { + mockRouteParams(); renderVolunteerGroups(link1); const searchInput = await screen.findByTestId('searchBy'); expect(searchInput).toBeInTheDocument(); @@ -174,6 +190,7 @@ describe('Testing VolunteerGroups Screen', () => { }); it('should render screen with No Groups', async () => { + mockRouteParams(); renderVolunteerGroups(link3); await waitFor(() => { @@ -183,6 +200,7 @@ describe('Testing VolunteerGroups Screen', () => { }); it('Error while fetching groups data', async () => { + mockRouteParams(); renderVolunteerGroups(link2); await waitFor(() => { @@ -191,6 +209,7 @@ describe('Testing VolunteerGroups Screen', () => { }); it('Open and close ViewModal', async () => { + mockRouteParams(); renderVolunteerGroups(link1); const viewGroupBtn = await screen.findAllByTestId('viewGroupBtn'); @@ -201,6 +220,7 @@ describe('Testing VolunteerGroups Screen', () => { }); it('Open and Close Delete Modal', async () => { + mockRouteParams(); renderVolunteerGroups(link1); const deleteGroupBtn = await screen.findAllByTestId('deleteGroupBtn'); @@ -211,6 +231,7 @@ describe('Testing VolunteerGroups Screen', () => { }); it('Open and close GroupModal (Edit)', async () => { + mockRouteParams(); renderVolunteerGroups(link1); const editGroupBtn = await screen.findAllByTestId('editGroupBtn'); @@ -221,6 +242,7 @@ describe('Testing VolunteerGroups Screen', () => { }); it('Open and close GroupModal (Create)', async () => { + mockRouteParams(); renderVolunteerGroups(link1); const createGroupBtn = await screen.findByTestId('createGroupBtn'); diff --git a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroups.tsx b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroups.tsx index fa98abc9f2..3c70b1db49 100644 --- a/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroups.tsx +++ b/src/screens/EventVolunteers/VolunteerGroups/VolunteerGroups.tsx @@ -16,7 +16,7 @@ import { } from '@mui/x-data-grid'; import { debounce, Stack } from '@mui/material'; import Avatar from 'components/Avatar/Avatar'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import { EVENT_VOLUNTEER_GROUP_LIST } from 'GraphQl/Queries/EventVolunteerQueries'; import VolunteerGroupModal from './VolunteerGroupModal'; import VolunteerGroupDeleteModal from './VolunteerGroupDeleteModal'; @@ -161,7 +161,7 @@ function volunteerGroups(): JSX.Element { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return (
{ const { _id, firstName, lastName, image } = params.row.leader; return ( @@ -194,14 +194,14 @@ function volunteerGroups(): JSX.Element { src={image} alt="Assignee" data-testid={`image${_id + 1}`} - className={styles.TableImage} + className={styles.TableImages} /> ) : (
@@ -219,7 +219,7 @@ function volunteerGroups(): JSX.Element { align: 'center', headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return (
@@ -235,7 +235,7 @@ function volunteerGroups(): JSX.Element { align: 'center', headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return (
@@ -252,7 +252,7 @@ function volunteerGroups(): JSX.Element { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return ( <> @@ -293,7 +293,7 @@ function volunteerGroups(): JSX.Element { return (
{/* Header with search, filter and Create Button */} -
+
{ setSearchValue(e.target.value); @@ -325,7 +325,7 @@ function volunteerGroups(): JSX.Element { @@ -350,7 +350,7 @@ function volunteerGroups(): JSX.Element { @@ -400,7 +400,7 @@ function volunteerGroups(): JSX.Element { ), }} sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} + getRowClassName={() => `${styles.rowBackgrounds}`} autoHeight rowHeight={65} rows={groups.map((group, index) => ({ diff --git a/src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.test.tsx b/src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.spec.tsx similarity index 91% rename from src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.test.tsx rename to src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.spec.tsx index cac8fe94f0..77fe028655 100644 --- a/src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.test.tsx +++ b/src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.spec.tsx @@ -22,11 +22,18 @@ import { toast } from 'react-toastify'; import type { InterfaceVolunteerCreateModal } from './VolunteerCreateModal'; import VolunteerCreateModal from './VolunteerCreateModal'; import userEvent from '@testing-library/user-event'; +import { vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +/** + * Mock implementation of the `react-toastify` module. + * Mocks the `toast` object with `success` and `error` methods to allow testing + * without triggering actual toast notifications. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -45,10 +52,10 @@ const t = { const itemProps: InterfaceVolunteerCreateModal[] = [ { isOpen: true, - hide: jest.fn(), + hide: vi.fn(), eventId: 'eventId', orgId: 'orgId', - refetchVolunteers: jest.fn(), + refetchVolunteers: vi.fn(), }, ]; diff --git a/src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.tsx b/src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.tsx index 6b4a1e3f0c..dee45376db 100644 --- a/src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.tsx +++ b/src/screens/EventVolunteers/Volunteers/VolunteerCreateModal.tsx @@ -1,7 +1,7 @@ import type { ChangeEvent } from 'react'; import { Button, Form, Modal } from 'react-bootstrap'; import type { InterfaceUserInfo } from 'utils/interfaces'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useMutation, useQuery } from '@apollo/client'; diff --git a/src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.test.tsx b/src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.spec.tsx similarity index 91% rename from src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.test.tsx rename to src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.spec.tsx index dd9d6d5985..575670a887 100644 --- a/src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.test.tsx +++ b/src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.spec.tsx @@ -15,11 +15,18 @@ import { toast } from 'react-toastify'; import type { InterfaceDeleteVolunteerModal } from './VolunteerDeleteModal'; import VolunteerDeleteModal from './VolunteerDeleteModal'; import userEvent from '@testing-library/user-event'; +import { vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +/** + * Mock implementation of the `react-toastify` module. + * Mocks the `toast` object with `success` and `error` methods to allow testing + * without triggering actual toast notifications. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -38,8 +45,8 @@ const t = { const itemProps: InterfaceDeleteVolunteerModal[] = [ { isOpen: true, - hide: jest.fn(), - refetchVolunteers: jest.fn(), + hide: vi.fn(), + refetchVolunteers: vi.fn(), volunteer: { _id: 'volunteerId1', hasAccepted: true, diff --git a/src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.tsx b/src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.tsx index 8f253fdf50..5c841a2f11 100644 --- a/src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.tsx +++ b/src/screens/EventVolunteers/Volunteers/VolunteerDeleteModal.tsx @@ -1,5 +1,5 @@ import { Button, Modal } from 'react-bootstrap'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useMutation } from '@apollo/client'; diff --git a/src/screens/EventVolunteers/Volunteers/VolunteerViewModal.test.tsx b/src/screens/EventVolunteers/Volunteers/VolunteerViewModal.spec.tsx similarity index 97% rename from src/screens/EventVolunteers/Volunteers/VolunteerViewModal.test.tsx rename to src/screens/EventVolunteers/Volunteers/VolunteerViewModal.spec.tsx index 155dba8464..e99fb47d20 100644 --- a/src/screens/EventVolunteers/Volunteers/VolunteerViewModal.test.tsx +++ b/src/screens/EventVolunteers/Volunteers/VolunteerViewModal.spec.tsx @@ -10,6 +10,7 @@ import { store } from 'state/store'; import i18n from 'utils/i18nForTest'; import type { InterfaceVolunteerViewModal } from './VolunteerViewModal'; import VolunteerViewModal from './VolunteerViewModal'; +import { vi } from 'vitest'; const t = { ...JSON.parse( @@ -24,7 +25,7 @@ const t = { const itemProps: InterfaceVolunteerViewModal[] = [ { isOpen: true, - hide: jest.fn(), + hide: vi.fn(), volunteer: { _id: 'volunteerId1', hasAccepted: true, @@ -51,7 +52,7 @@ const itemProps: InterfaceVolunteerViewModal[] = [ }, { isOpen: true, - hide: jest.fn(), + hide: vi.fn(), volunteer: { _id: 'volunteerId2', hasAccepted: false, diff --git a/src/screens/EventVolunteers/Volunteers/VolunteerViewModal.tsx b/src/screens/EventVolunteers/Volunteers/VolunteerViewModal.tsx index 0904d34b9c..830bacf8cc 100644 --- a/src/screens/EventVolunteers/Volunteers/VolunteerViewModal.tsx +++ b/src/screens/EventVolunteers/Volunteers/VolunteerViewModal.tsx @@ -1,6 +1,6 @@ import { Button, Form, Modal } from 'react-bootstrap'; import type { InterfaceEventVolunteerInfo } from 'utils/interfaces'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -83,14 +83,14 @@ const VolunteerViewModal: React.FC = ({ src={user.image} alt="Volunteer" data-testid="volunteer_image" - className={styles.TableImage} + className={styles.TableImages} /> ) : (
{ ); }; +/** Mock useParams to provide consistent test data */ + describe('Testing Volunteers Screen', () => { beforeAll(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId', eventId: 'eventId' }), - })); + vi.mock('react-router-dom', async () => { + const actualDom = await vi.importActual('react-router-dom'); + return { + ...actualDom, + useParams: vi.fn(), + }; + }); }); afterAll(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should redirect to fallback URL if URL params are undefined', async () => { + vi.mocked(useParams).mockReturnValue({ orgId: '', eventId: '' }); render( @@ -95,12 +102,21 @@ describe('Testing Volunteers Screen', () => { }); it('should render Volunteers screen', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); + renderVolunteers(link1); const searchInput = await screen.findByTestId('searchBy'); expect(searchInput).toBeInTheDocument(); }); it('Check Sorting Functionality', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link1); const searchInput = await screen.findByTestId('searchBy'); expect(searchInput).toBeInTheDocument(); @@ -134,6 +150,10 @@ describe('Testing Volunteers Screen', () => { }); it('Filter Volunteers by status (All)', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link1); const filterBtn = await screen.findByTestId('filter'); @@ -151,6 +171,10 @@ describe('Testing Volunteers Screen', () => { }); it('Filter Volunteers by status (Pending)', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link1); const filterBtn = await screen.findByTestId('filter'); @@ -168,6 +192,10 @@ describe('Testing Volunteers Screen', () => { }); it('Filter Volunteers by status (Accepted)', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link1); const filterBtn = await screen.findByTestId('filter'); @@ -185,6 +213,10 @@ describe('Testing Volunteers Screen', () => { }); it('Search', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link1); const searchInput = await screen.findByTestId('searchBy'); expect(searchInput).toBeInTheDocument(); @@ -197,6 +229,10 @@ describe('Testing Volunteers Screen', () => { }); it('should render screen with No Volunteers', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link3); await waitFor(() => { @@ -206,6 +242,10 @@ describe('Testing Volunteers Screen', () => { }); it('Error while fetching volunteers data', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link2); await waitFor(() => { @@ -214,6 +254,10 @@ describe('Testing Volunteers Screen', () => { }); it('Open and close Volunteer Modal (View)', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link1); const viewItemBtn = await screen.findAllByTestId('viewItemBtn'); @@ -224,6 +268,10 @@ describe('Testing Volunteers Screen', () => { }); it('Open and Close Volunteer Modal (Delete)', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link1); const deleteItemBtn = await screen.findAllByTestId('deleteItemBtn'); @@ -234,6 +282,10 @@ describe('Testing Volunteers Screen', () => { }); it('Open and close Volunteer Modal (Create)', async () => { + vi.mocked(useParams).mockReturnValue({ + orgId: 'orgId', + eventId: 'eventId', + }); renderVolunteers(link1); const addVolunteerBtn = await screen.findByTestId('addVolunteerBtn'); diff --git a/src/screens/EventVolunteers/Volunteers/Volunteers.tsx b/src/screens/EventVolunteers/Volunteers/Volunteers.tsx index 770bd35ef4..875431ad6f 100644 --- a/src/screens/EventVolunteers/Volunteers/Volunteers.tsx +++ b/src/screens/EventVolunteers/Volunteers/Volunteers.tsx @@ -20,7 +20,7 @@ import { } from '@mui/x-data-grid'; import { Chip, debounce, Stack } from '@mui/material'; import Avatar from 'components/Avatar/Avatar'; -import styles from '../EventVolunteers.module.css'; +import styles from '../../../style/app.module.css'; import { EVENT_VOLUNTEER_LIST } from 'GraphQl/Queries/EventVolunteerQueries'; import type { InterfaceEventVolunteerInfo } from 'utils/interfaces'; import VolunteerCreateModal from './VolunteerCreateModal'; @@ -179,7 +179,7 @@ function volunteers(): JSX.Element { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { const { _id, firstName, lastName, image } = params.row.user; return ( @@ -192,7 +192,7 @@ function volunteers(): JSX.Element { src={image} alt="volunteer" data-testid="volunteer_image" - className={styles.TableImage} + className={styles.TableImages} /> ) : (
@@ -200,7 +200,7 @@ function volunteers(): JSX.Element { key={_id + '1'} dataTestId="volunteer_avatar" containerStyle={styles.imageContainer} - avatarStyle={styles.TableImage} + avatarStyle={styles.TableImages} name={firstName + ' ' + lastName} alt={firstName + ' ' + lastName} /> @@ -219,7 +219,7 @@ function volunteers(): JSX.Element { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return ( { return (
{ return ( @@ -278,7 +278,7 @@ function volunteers(): JSX.Element { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return ( <> @@ -310,7 +310,7 @@ function volunteers(): JSX.Element { return (
{/* Header with search, filter and Create Button */} -
+
{ setSearchValue(e.target.value); @@ -341,7 +341,7 @@ function volunteers(): JSX.Element { @@ -365,7 +365,7 @@ function volunteers(): JSX.Element { @@ -421,7 +421,7 @@ function volunteers(): JSX.Element { ), }} sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} + getRowClassName={() => `${styles.rowBackgrounds}`} autoHeight rowHeight={65} rows={volunteers.map((volunteer, index) => ({ diff --git a/src/screens/ForgotPassword/ForgotPassword.module.css b/src/screens/ForgotPassword/ForgotPassword.module.css deleted file mode 100644 index 74e09aecc6..0000000000 --- a/src/screens/ForgotPassword/ForgotPassword.module.css +++ /dev/null @@ -1,71 +0,0 @@ -.pageWrapper { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100vh; -} - -.cardBody { - padding: 2rem; - background-color: #fff; - border-radius: 0.8rem; - border: 1px solid var(--bs-gray-200); -} - -.keyWrapper { - height: 72px; - width: 72px; - transform: rotate(180deg); - position: relative; - overflow: hidden; - display: block; - display: flex; - justify-content: center; - align-items: center; - border-radius: 50%; - margin: 1rem auto; -} - -.keyWrapper .themeOverlay { - position: absolute; - background-color: var(--bs-primary); - height: 100%; - width: 100%; - opacity: 0.15; -} - -.keyWrapper .keyLogo { - height: 42px; - width: 42px; - -webkit-animation: zoomIn 0.3s ease-in-out; - animation: zoomIn 0.3s ease-in-out; -} - -@-webkit-keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} diff --git a/src/screens/ForgotPassword/ForgotPassword.tsx b/src/screens/ForgotPassword/ForgotPassword.tsx index 663960572b..83f20b4375 100644 --- a/src/screens/ForgotPassword/ForgotPassword.tsx +++ b/src/screens/ForgotPassword/ForgotPassword.tsx @@ -16,7 +16,7 @@ import { Form } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; import { useTranslation } from 'react-i18next'; import { errorHandler } from 'utils/errorHandler'; -import styles from './ForgotPassword.module.css'; +import styles from 'style/app.module.css'; import useLocalStorage from 'utils/useLocalstorage'; /** @@ -162,7 +162,7 @@ const ForgotPassword = (): JSX.Element => {
-
+
diff --git a/src/screens/FundCampaignPledge/FundCampaignPledge.module.css b/src/screens/FundCampaignPledge/FundCampaignPledge.module.css deleted file mode 100644 index cdf4476267..0000000000 --- a/src/screens/FundCampaignPledge/FundCampaignPledge.module.css +++ /dev/null @@ -1,273 +0,0 @@ -.pledgeContainer { - margin: 0.6rem 0; -} - -.container { - min-height: 100vh; -} - -.pledgeModal { - max-width: 80vw; - margin-top: 2vh; - margin-left: 13vw; -} - -.titlemodal { - color: #707070; - font-weight: 600; - font-size: 32px; - width: 65%; - margin-bottom: 0px; -} - -.modalCloseBtn { - width: 40px; - height: 40px; - padding: 1rem; - display: flex; - justify-content: center; - align-items: center; -} - -.greenregbtn { - margin: 1rem 0 0; - margin-top: 15px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 10px 10px; - border-radius: 5px; - background-color: #31bb6b; - width: 100%; - font-size: 16px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; - width: 100%; -} -.message { - margin-top: 25%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.errorIcon { - transform: scale(1.5); - color: var(--bs-danger); - margin-bottom: 1rem; -} - -.btnsContainer { - display: flex; - gap: 0.8rem; - margin: 2.2rem 0 0.8rem 0; -} - -.btnsContainer .input { - flex: 1; - min-width: 18rem; - position: relative; -} - -.btnsContainer input { - outline: 1px solid var(--bs-gray-400); -} - -.btnsContainer .input button { - width: 52px; -} - -.inputField { - background-color: white; - box-shadow: 0 1px 1px #31bb6b; -} - -.dropdown { - background-color: white; - border: 1px solid #31bb6b; - position: relative; - display: inline-block; - color: #31bb6b; -} - -.tableHeader { - background-color: var(--bs-primary); - color: var(--bs-white); - font-size: 1rem; -} - -.rowBackground { - background-color: var(--bs-white); - max-height: 120px; -} - -.TableImage { - object-fit: cover; - width: 25px !important; - height: 25px !important; - border-radius: 100% !important; -} - -.avatarContainer { - width: 28px; - height: 26px; -} - -.imageContainer { - display: flex; - align-items: center; - justify-content: center; -} - -.pledgerContainer { - display: flex; - align-items: center; - justify-content: center; - margin: 0.1rem 0.25rem; - gap: 0.25rem; - padding: 0.25rem 0.45rem; - border-radius: 0.35rem; - background-color: #31bb6b33; - height: 2.2rem; - margin-top: 0.75rem; -} - -.noOutline input { - outline: none; -} - -.overviewContainer { - display: flex; - gap: 7rem; - width: 100%; - justify-content: space-between; - margin: 1.5rem 0 0 0; - padding: 1.25rem 2rem; - background-color: rgba(255, 255, 255, 0.591); - - box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px; - border-radius: 0.5rem; -} - -.titleContainer { - display: flex; - flex-direction: column; - gap: 0.6rem; -} - -.titleContainer h3 { - font-size: 1.75rem; - font-weight: 750; - color: #5e5e5e; - margin-top: 0.2rem; -} - -.titleContainer span { - font-size: 0.9rem; - margin-left: 0.5rem; - font-weight: lighter; - color: #707070; -} - -.raisedAmount { - display: flex; - justify-content: center; - align-items: center; - font-size: 1.25rem; - font-weight: 750; - color: #5e5e5e; -} - -.progressContainer { - display: flex; - flex-direction: column; - gap: 0.5rem; - flex-grow: 1; -} - -.progress { - margin-top: 0.2rem; - display: flex; - flex-direction: column; - gap: 0.3rem; -} - -.endpoints { - display: flex; - position: relative; - font-size: 0.85rem; -} - -.start { - position: absolute; - top: 0px; -} - -.end { - position: absolute; - top: 0px; - right: 0px; -} - -.moreContainer { - display: flex; - align-items: center; -} - -.moreContainer:hover { - text-decoration: underline; - cursor: pointer; -} - -.popup { - z-index: 50; - border-radius: 0.5rem; - font-family: sans-serif; - font-weight: 500; - font-size: 0.875rem; - margin-top: 0.5rem; - padding: 0.75rem; - border: 1px solid #e2e8f0; - background-color: white; - color: #1e293b; - box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 0.15); - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.popupExtra { - max-height: 15rem; - overflow-y: auto; -} - -.toggleGroup { - width: 50%; - min-width: 27.75rem; - margin: 0.5rem 0rem; -} - -.toggleBtn { - padding: 0rem; - height: 30px; - display: flex; - justify-content: center; - align-items: center; -} - -.toggleBtn:hover { - color: #31bb6b !important; -} - -input[type='radio']:checked + label { - background-color: #31bb6a50 !important; -} - -input[type='radio']:checked + label:hover { - color: black !important; -} diff --git a/src/screens/FundCampaignPledge/FundCampaignPledge.tsx b/src/screens/FundCampaignPledge/FundCampaignPledge.tsx index d14ee9de06..8942265eea 100644 --- a/src/screens/FundCampaignPledge/FundCampaignPledge.tsx +++ b/src/screens/FundCampaignPledge/FundCampaignPledge.tsx @@ -9,7 +9,7 @@ import { Button, Dropdown, Form } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Navigate, useParams } from 'react-router-dom'; import { currencySymbols } from 'utils/currency'; -import styles from './FundCampaignPledge.module.css'; +import styles from '../../style/app.module.css'; import PledgeDeleteModal from './PledgeDeleteModal'; import PledgeModal from './PledgeModal'; import { Breadcrumbs, Link, Stack, Typography } from '@mui/material'; @@ -233,14 +233,14 @@ const fundCampaignPledge = (): JSX.Element => { src={user.image} alt="pledge" data-testid={`image${index + 1}`} - className={styles.TableImage} + className={styles.TableImagePledge} /> ) : (
@@ -425,14 +425,14 @@ const fundCampaignPledge = (): JSX.Element => { > setProgressIndicator('pledged')} />
-
-
+
+
setSearchTerm(e.target.value)} data-testid="searchPledger" @@ -557,7 +557,7 @@ const fundCampaignPledge = (): JSX.Element => { ), }} sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} + getRowClassName={() => `${styles.rowBackgroundPledge}`} autoHeight rowHeight={65} rows={pledges.map((pledge) => ({ @@ -607,14 +607,14 @@ const fundCampaignPledge = (): JSX.Element => { src={user.image} alt="pledger" data-testid={`extraImage${index + 1}`} - className={styles.TableImage} + className={styles.TableImagePledge} /> ) : (
= ({ = ({ format="DD/MM/YYYY" label={tCommon('startDate')} value={dayjs(pledgeStartDate)} - className={styles.noOutline} + className={styles.noOutlinePledge} onChange={(date: Dayjs | null): void => { if (date) { setFormState({ @@ -280,7 +280,7 @@ const PledgeModal: React.FC = ({ { if (date) { @@ -327,7 +327,7 @@ const PledgeModal: React.FC = ({ { if (parseInt(e.target.value) > 0) { @@ -343,7 +343,7 @@ const PledgeModal: React.FC = ({ {/* Button to submit the pledge form */}
-
+
- - - {headerTitles.map((title: string, index: number) => { + {isLoading && ( + + )} + { + loadMoreUsers(displayedUsers.length, perPageResult); + }} + loader={ + + } + hasMore={hasMore} + className={styles.listBox} + data-testid="users-list" + endMessage={ +
+
{tCommon('endOfResults')}
+
+ } + > +
+ + + {headerTitles.map((title: string, index: number) => { + return ( + + ); + })} + + + + {usersData && + displayedUsers.map( + (user: InterfaceQueryUserListItem, index: number) => { return ( - + ); - })} - - - - {usersData && - displayedUsers.map( - (user: InterfaceQueryUserListItem, index: number) => { - return ( - - ); - }, - )} - -
+ {title} +
- {title} -
- - )} + }, + )} + + +
)} ); }; - export default Users; diff --git a/src/screens/Users/UsersMocks.ts b/src/screens/Users/UsersMocks.ts index ff346a1c97..f7908cf675 100644 --- a/src/screens/Users/UsersMocks.ts +++ b/src/screens/Users/UsersMocks.ts @@ -478,7 +478,6 @@ export const EMPTY_MOCKS = [ { request: { query: USER_LIST, - variables: { first: 12, skip: 0, diff --git a/src/setup/askForCustomPort/askForCustomPort.spec.ts b/src/setup/askForCustomPort/askForCustomPort.spec.ts new file mode 100644 index 0000000000..08e67cec00 --- /dev/null +++ b/src/setup/askForCustomPort/askForCustomPort.spec.ts @@ -0,0 +1,106 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import inquirer from 'inquirer'; +import { askForCustomPort, validatePort } from './askForCustomPort'; + +vi.mock('inquirer'); + +describe('askForCustomPort', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('basic port validation', () => { + it('should return default port if user provides no input', async () => { + vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ + customPort: '4321', + }); + + const result = await askForCustomPort(); + expect(result).toBe(4321); + }); + + it('should return user-provided port', async () => { + vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ + customPort: '8080', + }); + + const result = await askForCustomPort(); + expect(result).toBe(8080); + }); + + it('should return validation error if port not between 1 and 65535', () => { + expect(validatePort('abcd')).toBe( + 'Please enter a valid port number between 1 and 65535.', + ); + expect(validatePort('-1')).toBe( + 'Please enter a valid port number between 1 and 65535.', + ); + expect(validatePort('70000')).toBe( + 'Please enter a valid port number between 1 and 65535.', + ); + }); + }); + + describe('retry mechanism', () => { + it('should handle invalid port input and prompt again', async () => { + vi.spyOn(inquirer, 'prompt') + .mockResolvedValueOnce({ customPort: 'abcd' }) + .mockResolvedValueOnce({ customPort: '8080' }); + + const result = await askForCustomPort(); + expect(result).toBe(8080); + }); + + it('should return default port after maximum retry attempts', async () => { + vi.spyOn(inquirer, 'prompt') + .mockResolvedValueOnce({ customPort: 'invalid-port-attempt1' }) + .mockResolvedValueOnce({ customPort: 'invalid-port-attempt2' }) + .mockResolvedValueOnce({ customPort: 'invalid-port-attempt3' }) + .mockResolvedValueOnce({ customPort: 'invalid-port-attempt4' }) + .mockResolvedValueOnce({ customPort: 'invalid-port-attempt5' }) + .mockResolvedValueOnce({ customPort: 'invalid-port-attempt6' }); + + const result = await askForCustomPort(); + expect(result).toBe(4321); + }); + }); + + describe('reserved ports', () => { + it('should return user-provided port after confirming reserved port', async () => { + vi.spyOn(inquirer, 'prompt') + .mockResolvedValueOnce({ customPort: '80' }) + .mockResolvedValueOnce({ confirmPort: true }); + + const result = await askForCustomPort(); + expect(result).toBe(80); + }); + + it('should re-prompt user for port if reserved port confirmation is denied', async () => { + vi.spyOn(inquirer, 'prompt') + .mockResolvedValueOnce({ customPort: '80' }) + .mockResolvedValueOnce({ confirmPort: false }) + .mockResolvedValueOnce({ customPort: '8080' }); + + const result = await askForCustomPort(); + expect(result).toBe(8080); + }); + + it('should return default port if reserved port confirmation is denied after maximum retry attempts', async () => { + vi.spyOn(inquirer, 'prompt') + .mockResolvedValueOnce({ customPort: '80' }) + .mockResolvedValueOnce({ confirmPort: false }) + .mockResolvedValueOnce({ customPort: '80' }) + .mockResolvedValueOnce({ confirmPort: false }) + .mockResolvedValueOnce({ customPort: '80' }) + .mockResolvedValueOnce({ confirmPort: false }) + .mockResolvedValueOnce({ customPort: '80' }) + .mockResolvedValueOnce({ confirmPort: false }) + .mockResolvedValueOnce({ customPort: '80' }) + .mockResolvedValueOnce({ confirmPort: false }) + .mockResolvedValueOnce({ customPort: '80' }); + + const result = await askForCustomPort(); + expect(result).toBe(4321); + }); + }); +}); diff --git a/src/setup/askForCustomPort/askForCustomPort.test.ts b/src/setup/askForCustomPort/askForCustomPort.test.ts deleted file mode 100644 index 0df6259ba1..0000000000 --- a/src/setup/askForCustomPort/askForCustomPort.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import inquirer from 'inquirer'; -import { askForCustomPort } from './askForCustomPort'; - -jest.mock('inquirer'); - -describe('askForCustomPort', () => { - test('should return default port if user provides no input', async () => { - jest - .spyOn(inquirer, 'prompt') - .mockResolvedValueOnce({ customPort: '4321' }); - - const result = await askForCustomPort(); - expect(result).toBe('4321'); - }); - - test('should return user-provided port', async () => { - jest - .spyOn(inquirer, 'prompt') - .mockResolvedValueOnce({ customPort: '8080' }); - - const result = await askForCustomPort(); - expect(result).toBe('8080'); - }); -}); diff --git a/src/setup/askForCustomPort/askForCustomPort.ts b/src/setup/askForCustomPort/askForCustomPort.ts index 8a923f678f..dd0fd51854 100644 --- a/src/setup/askForCustomPort/askForCustomPort.ts +++ b/src/setup/askForCustomPort/askForCustomPort.ts @@ -1,14 +1,63 @@ import inquirer from 'inquirer'; -export async function askForCustomPort(): Promise { - const { customPort } = await inquirer.prompt([ +const DEFAULT_PORT = 4321; +const MAX_RETRY_ATTEMPTS = 5; + +export function validatePort(input: string): string | boolean { + const port = Number(input); + if ( + Number.isNaN(port) || + !Number.isInteger(port) || + port <= 0 || + port > 65535 + ) { + return 'Please enter a valid port number between 1 and 65535.'; + } + return true; +} + +export async function reservedPortWarning(port: number): Promise { + const { confirmPort } = await inquirer.prompt<{ confirmPort: boolean }>([ { - type: 'input', - name: 'customPort', - message: - 'Enter custom port for development server (leave blank for default 4321):', - default: 4321, + type: 'confirm', + name: 'confirmPort', + message: `Port ${port} is a reserved port. Are you sure you want to use it?`, + default: false, }, ]); - return customPort; + + return confirmPort; +} + +export async function askForCustomPort(): Promise { + let remainingAttempts = MAX_RETRY_ATTEMPTS; + + while (remainingAttempts--) { + const { customPort } = await inquirer.prompt<{ customPort: string }>([ + { + type: 'input', + name: 'customPort', + message: `Enter custom port for development server (leave blank for default ${DEFAULT_PORT}):`, + default: DEFAULT_PORT.toString(), + validate: validatePort, + }, + ]); + + if (customPort && validatePort(customPort) === true) { + if (Number(customPort) >= 1024) { + return Number(customPort); + } + + if ( + Number(customPort) < 1024 && + (await reservedPortWarning(Number(customPort))) + ) { + return Number(customPort); + } + } + } + console.log( + `\nMaximum attempts reached. Using default port ${DEFAULT_PORT}.`, + ); + return DEFAULT_PORT; } diff --git a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.test.ts b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.test.ts index b1490222b4..3a11a0d799 100644 --- a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.test.ts +++ b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.test.ts @@ -30,17 +30,9 @@ describe('askForTalawaApiUrl', () => { }); test('should return the default endpoint when the user does not enter anything', async () => { - const mockPrompt = jest - .spyOn(inquirer, 'prompt') - .mockImplementation(async (questions: any) => { - const answers: Record = {}; - questions.forEach( - (question: { name: string | number; default: any }) => { - answers[question.name] = question.default; - }, - ); - return answers; - }); + const mockPrompt = jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ + endpoint: 'http://localhost:4000/graphql/', + }); const result = await askForTalawaApiUrl(); diff --git a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts index 97daa1ac89..713ed7dc68 100644 --- a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts +++ b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts @@ -1,7 +1,7 @@ import inquirer from 'inquirer'; export async function askForTalawaApiUrl(): Promise { - const { endpoint } = await inquirer.prompt([ + const { endpoint } = await inquirer.prompt<{ endpoint: string }>([ { type: 'input', name: 'endpoint', diff --git a/src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.test.ts b/src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.spec.ts similarity index 69% rename from src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.test.ts rename to src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.spec.ts index 3fa612bd25..926444d8a6 100644 --- a/src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.test.ts +++ b/src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.spec.ts @@ -1,15 +1,20 @@ import fs from 'fs'; +import { vi } from 'vitest'; import inquirer from 'inquirer'; import { askForTalawaApiUrl } from './askForTalawaApiUrl'; -jest.mock('fs'); -jest.mock('inquirer', () => ({ - prompt: jest.fn(), -})); +vi.mock('fs'); +vi.mock('inquirer', async () => { + const actual = await vi.importActual('inquirer'); + return { + ...actual, + prompt: vi.fn(), + }; +}); describe('WebSocket URL Configuration', () => { beforeEach(() => { - jest.resetAllMocks(); + vi.clearAllMocks(); }); test('should convert http URL to ws WebSocket URL', async () => { @@ -27,12 +32,12 @@ describe('WebSocket URL Configuration', () => { }); test('should retain default WebSocket URL if no new endpoint is provided', async () => { - jest - .spyOn(inquirer, 'prompt') - .mockResolvedValueOnce({ endpoint: 'http://localhost:4000/graphql/' }); + vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ + endpoint: 'http://localhost:4000/graphql/', + }); await askForTalawaApiUrl(); - const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync'); + const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync'); expect(writeFileSyncSpy).not.toHaveBeenCalled(); }); }); diff --git a/src/setup/checkConnection/checkConnection.test.ts b/src/setup/checkConnection/checkConnection.spec.ts similarity index 84% rename from src/setup/checkConnection/checkConnection.test.ts rename to src/setup/checkConnection/checkConnection.spec.ts index c6f5251bdf..724f1a49dd 100644 --- a/src/setup/checkConnection/checkConnection.test.ts +++ b/src/setup/checkConnection/checkConnection.spec.ts @@ -1,8 +1,8 @@ import { checkConnection } from './checkConnection'; +import { vi, describe, beforeEach, it, expect } from 'vitest'; +vi.mock('node-fetch'); -jest.mock('node-fetch'); - -global.fetch = jest.fn((url) => { +global.fetch = vi.fn((url) => { if (url === 'http://example.com/graphql/') { const responseInit: ResponseInit = { status: 200, @@ -22,11 +22,11 @@ global.fetch = jest.fn((url) => { describe('checkConnection', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); test('should return true and log success message if the connection is successful', async () => { - jest.spyOn(console, 'log').mockImplementation((string) => string); + vi.spyOn(console, 'log').mockImplementation((string) => string); const result = await checkConnection('http://example.com/graphql/'); expect(result).toBe(true); @@ -39,7 +39,7 @@ describe('checkConnection', () => { }); it('should return false and log error message if the connection fails', async () => { - jest.spyOn(console, 'log').mockImplementation((string) => string); + vi.spyOn(console, 'log').mockImplementation((string) => string); const result = await checkConnection( 'http://example_not_working.com/graphql/', ); diff --git a/src/setup/checkEnvFile/checkEnvFile.test.ts b/src/setup/checkEnvFile/checkEnvFile.spec.ts similarity index 62% rename from src/setup/checkEnvFile/checkEnvFile.test.ts rename to src/setup/checkEnvFile/checkEnvFile.spec.ts index a23976db4a..c2b75db0a6 100644 --- a/src/setup/checkEnvFile/checkEnvFile.test.ts +++ b/src/setup/checkEnvFile/checkEnvFile.spec.ts @@ -1,11 +1,22 @@ import fs from 'fs'; import { checkEnvFile } from './checkEnvFile'; +import { vi } from 'vitest'; -jest.mock('fs'); +/** + * This file contains unit tests for the `checkEnvFile` function. + * + * The tests cover: + * - Behavior when the `.env` file is missing required keys and appending them appropriately. + * - Ensuring no changes are made when all keys are present in the `.env` file. + * + * These tests utilize Vitest for test execution and mock the `fs` module to simulate file operations. + */ + +vi.mock('fs'); describe('checkEnvFile', () => { beforeEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('should append missing keys to the .env file', () => { @@ -13,13 +24,12 @@ describe('checkEnvFile', () => { const envExampleContent = 'EXISTING_KEY=existing_value\nNEW_KEY=default_value\n'; - jest - .spyOn(fs, 'readFileSync') + vi.spyOn(fs, 'readFileSync') .mockReturnValueOnce(envContent) .mockReturnValueOnce(envExampleContent) .mockReturnValueOnce(envExampleContent); - jest.spyOn(fs, 'appendFileSync'); + vi.spyOn(fs, 'appendFileSync'); checkEnvFile(); @@ -33,12 +43,11 @@ describe('checkEnvFile', () => { const envContent = 'EXISTING_KEY=existing_value\n'; const envExampleContent = 'EXISTING_KEY=existing_value\n'; - jest - .spyOn(fs, 'readFileSync') + vi.spyOn(fs, 'readFileSync') .mockReturnValueOnce(envContent) .mockReturnValueOnce(envExampleContent); - jest.spyOn(fs, 'appendFileSync'); + vi.spyOn(fs, 'appendFileSync'); checkEnvFile(); diff --git a/src/setupTests.ts b/src/setupTests.ts index eac7093309..19c34e040a 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -3,7 +3,6 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; - global.fetch = jest.fn(); import { format } from 'util'; @@ -32,4 +31,4 @@ jestPreviewConfigure({ autoPreview: true, }); -jest.setTimeout(15000); +jest.setTimeout(18000); diff --git a/src/state/action-creators/index.test.ts b/src/state/action-creators/index.spec.ts similarity index 90% rename from src/state/action-creators/index.test.ts rename to src/state/action-creators/index.spec.ts index 33aa642b8a..141eb679d9 100644 --- a/src/state/action-creators/index.test.ts +++ b/src/state/action-creators/index.spec.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { updateInstalled, installPlugin, @@ -10,7 +11,7 @@ describe('Testing rc/state/action-creators/index.ts', () => { const temp = updateInstalled('testPlug'); expect(typeof temp).toBe('function'); //stubbing the childfunction to check execution - const childFunction = jest.fn(); + const childFunction = vi.fn(); temp(childFunction); expect(childFunction).toHaveBeenCalled(); }); @@ -20,7 +21,7 @@ describe('Testing rc/state/action-creators/index.ts', () => { const temp = installPlugin('testPlug'); expect(typeof temp).toBe('function'); //stubbing the childfunction to check execution - const childFunction = jest.fn(); + const childFunction = vi.fn(); temp(childFunction); expect(childFunction).toHaveBeenCalled(); }); @@ -30,7 +31,7 @@ describe('Testing rc/state/action-creators/index.ts', () => { const temp = removePlugin('testPlug'); expect(typeof temp).toBe('function'); //stubbing the childfunction to check execution - const childFunction = jest.fn(); + const childFunction = vi.fn(); temp(childFunction); expect(childFunction).toHaveBeenCalled(); }); @@ -40,7 +41,7 @@ describe('Testing rc/state/action-creators/index.ts', () => { const temp = updatePluginLinks('testPlug'); expect(typeof temp).toBe('function'); //stubbing the childfunction to check execution - const childFunction = jest.fn(); + const childFunction = vi.fn(); temp(childFunction); expect(childFunction).toHaveBeenCalled(); }); diff --git a/src/state/helpers/Action.test.ts b/src/state/helpers/Action.spec.ts similarity index 53% rename from src/state/helpers/Action.test.ts rename to src/state/helpers/Action.spec.ts index a971c6c160..cc81153617 100644 --- a/src/state/helpers/Action.test.ts +++ b/src/state/helpers/Action.spec.ts @@ -1,8 +1,11 @@ import type { InterfaceAction } from './Action'; test('Testing Reducer Action Interface', () => { - ({ + const action = { type: 'STRING_ACTION_TYPE', payload: 'ANY_PAYLOAD', - }) as InterfaceAction; + } as InterfaceAction; + + expect(action.type).toBe('STRING_ACTION_TYPE'); + expect(action.payload).toBe('ANY_PAYLOAD'); }); diff --git a/src/state/reducers/pluginReducer.test.ts b/src/state/reducers/pluginReducer.spec.ts similarity index 100% rename from src/state/reducers/pluginReducer.test.ts rename to src/state/reducers/pluginReducer.spec.ts diff --git a/src/state/reducers/routesReducer.test.ts b/src/state/reducers/routesReducer.spec.ts similarity index 100% rename from src/state/reducers/routesReducer.test.ts rename to src/state/reducers/routesReducer.spec.ts diff --git a/src/state/reducers/userRoutersReducer.test.ts b/src/state/reducers/userRoutersReducer.spec.ts similarity index 82% rename from src/state/reducers/userRoutersReducer.test.ts rename to src/state/reducers/userRoutersReducer.spec.ts index e2987dcd38..9eb5a5327f 100644 --- a/src/state/reducers/userRoutersReducer.test.ts +++ b/src/state/reducers/userRoutersReducer.spec.ts @@ -16,8 +16,10 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: 'user/events/undefined' }, { name: 'Volunteer', url: 'user/volunteer/undefined' }, { name: 'Donate', url: 'user/donate/undefined' }, + { name: 'Chat', url: 'user/chat/undefined' }, { name: 'Campaigns', url: 'user/campaigns/undefined' }, { name: 'My Pledges', url: 'user/pledges/undefined' }, + { name: 'Leave Organization', url: 'user/leaveorg/undefined' }, ], components: [ { @@ -38,12 +40,18 @@ describe('Testing Routes reducer', () => { component: 'VolunteerManagement', }, { name: 'Donate', comp_id: 'donate', component: 'Donate' }, + { name: 'Chat', comp_id: 'chat', component: 'Chat' }, { name: 'Campaigns', comp_id: 'campaigns', component: 'Campaigns', }, { name: 'My Pledges', comp_id: 'pledges', component: 'Pledges' }, + { + name: 'Leave Organization', + comp_id: 'leaveorg', + component: 'LeaveOrganization', + }, ], }); }); @@ -62,8 +70,10 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: 'user/events/orgId' }, { name: 'Volunteer', url: 'user/volunteer/orgId' }, { name: 'Donate', url: 'user/donate/orgId' }, + { name: 'Chat', url: 'user/chat/orgId' }, { name: 'Campaigns', url: 'user/campaigns/orgId' }, { name: 'My Pledges', url: 'user/pledges/orgId' }, + { name: 'Leave Organization', url: 'user/leaveorg/orgId' }, ], components: [ { @@ -84,12 +94,18 @@ describe('Testing Routes reducer', () => { component: 'VolunteerManagement', }, { name: 'Donate', comp_id: 'donate', component: 'Donate' }, + { name: 'Chat', comp_id: 'chat', component: 'Chat' }, { name: 'Campaigns', comp_id: 'campaigns', component: 'Campaigns', }, { name: 'My Pledges', comp_id: 'pledges', component: 'Pledges' }, + { + name: 'Leave Organization', + comp_id: 'leaveorg', + component: 'LeaveOrganization', + }, ], }); }); diff --git a/src/state/reducers/userRoutesReducer.ts b/src/state/reducers/userRoutesReducer.ts index e1bf5de0dc..90498e4bf6 100644 --- a/src/state/reducers/userRoutesReducer.ts +++ b/src/state/reducers/userRoutesReducer.ts @@ -57,12 +57,18 @@ const components: ComponentType[] = [ { name: 'Events', comp_id: 'events', component: 'Events' }, { name: 'Volunteer', comp_id: 'volunteer', component: 'VolunteerManagement' }, { name: 'Donate', comp_id: 'donate', component: 'Donate' }, + { name: 'Chat', comp_id: 'chat', component: 'Chat' }, { name: 'Campaigns', comp_id: 'campaigns', component: 'Campaigns', }, { name: 'My Pledges', comp_id: 'pledges', component: 'Pledges' }, + { + name: 'Leave Organization', + comp_id: 'leaveorg', + component: 'LeaveOrganization', + }, ]; const generateRoutes = ( diff --git a/src/state/store.test.tsx b/src/state/store.spec.tsx similarity index 100% rename from src/state/store.test.tsx rename to src/state/store.spec.tsx diff --git a/src/style/app.module.css b/src/style/app.module.css index e0382fe70c..137ff0bd01 100644 --- a/src/style/app.module.css +++ b/src/style/app.module.css @@ -1,11 +1,8 @@ :root { - --dropdown-border-color: #555555; - --dropdown-text-color: #555555; - --high-contrast-text: #494949; - /* Color contrast ratio: 9:1 (exceeds WCAG AAA) */ - --high-contrast-border: #2c2c2c; + --brown-color: #555555; --dropdown-hover-color: #eff1f7; --grey-bg-color: #eaebef; + --grey-border-box-color: #e8e5e5; --subtle-blue-grey: #7c9beb; --subtle-blue-grey-hover: #5f7e91; --modal-width: 670px; @@ -16,10 +13,17 @@ --search-button-bg: #a8c7fa; --search-button-border: #555555; --table-image-size: 50px; + --table-image-small-size: 25px; + --bs-primary: #0056b3; + --bs-white: #fff; + --toggle-button-bg: #1e4e8c; --table-head-bg: var( --bs-primary, blue ); /* Assuming var(--bs-primary) is defined elsewhere */ + --loader-size: 10em; + --loader-border-width: 1.1em; + --loader-color: #febc59; --table-head-color: white; --table-header-color: var(--bs-greyish-black, black); --table-head-radius: 20px; @@ -27,27 +31,157 @@ --tablerow-bg-color: #eff1f7; --row-background: var(--bs-white, white); --font-size-header: 16px; + --input-area-color: #f1f3f6; + --date-picker-background: #f2f2f2; + --grey-bg-color-dark: #707070; +} +.fonts { + color: #707070; } -.noOutline input { - outline: none; +.fonts > span { + font-weight: 600; } -.noOutline:is(:hover, :focus, :active, :focus-visible, .show) { - outline: none !important; +.cards > h2 { + font-size: 19px; +} +.cards > h3 { + font-size: 17px; +} +.cards > p { + font-size: 14px; + margin-top: -5px; + margin-bottom: 7px; } -.closeButton { - color: var(--delete-button-color); - margin-right: 5px; - background-color: var(--delete-button-bg); - border: white; +.cards:hover { + filter: brightness(0.8); +} +.cards:hover::before { + opacity: 0.5; } -.closeButton:hover { - color: var(--delete-button-bg) !important; - background-color: var(--delete-button-color) !important; - border: white; +.cards:hover::after { + opacity: 1; + mix-blend-mode: normal; +} + +.cards:last-child:nth-last-child(odd) { + grid-column: auto / span 2; +} + +.cards:first-child:nth-last-child(even), +.cards:first-child:nth-last-child(even) ~ .box { + grid-column: auto / span 1; +} + +.capacityLabel { + background-color: var(--bs-primary); + color: white; + height: 22.19px; + font-size: 12px; + font-weight: bolder; + padding: 0.1rem 0.3rem; + border-radius: 0.5rem; + position: relative; + overflow: hidden; +} + +.capacityLabel svg { + margin-bottom: 3px; +} + +.sidebar { + z-index: 0; + padding-top: 5px; + margin: 0; + height: 100%; +} +.sidebar:after { + background-color: #f7f7f7; + position: absolute; + width: 2px; + height: 600px; + top: 10px; + left: 94%; + display: block; +} +.sidebarsticky { + padding-left: 45px; + margin-top: 7px; +} +.sidebarsticky > p { + margin-top: -10px; +} + +.logintitle { + color: #707070; + font-weight: 600; + font-size: 20px; + margin-bottom: 30px; + padding-bottom: 5px; + border-bottom: 3px solid #31bb6b; + width: 15%; +} + +.searchtitle { + color: #707070; + font-weight: 600; + font-size: 18px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #31bb6b; + width: 60%; +} + +.admindetails { + display: flex; + justify-content: space-between; +} + +.admindetails > p { + margin-top: -12px; + margin-right: 30px; +} + +.mainpageright > hr { + margin-top: 20px; + width: 100%; + margin-left: -15px; + margin-right: -15px; + margin-bottom: 20px; +} + +.justifysp { + display: flex; + justify-content: space-between; +} + +@media screen and (max-width: 575.5px) { + .justifysp { + padding-left: 55px; + display: flex; + justify-content: space-between; + width: 100%; + } +} + +.sidebarsticky > input { + text-decoration: none; + margin-bottom: 50px; + border-color: var(--grey-border-box-color); + width: 80%; + border-radius: 7px; + padding-top: 5px; + padding-bottom: 5px; + padding-right: 10px; + padding-left: 10px; + box-shadow: none; +} + +.noOutline:is(:hover, :focus, :active, :focus-visible, .show) { + outline: none !important; } .modalContent { @@ -66,9 +200,9 @@ } .dropdown { - background-color: white; - border: 1px solid var(--dropdown-border-color); - color: var(--dropdown-text-color); + background-color: var(--bs-white); + border: 1px solid var(--brown-color); + color: var(--brown-color); position: relative; display: inline-block; margin-top: 10px; @@ -77,27 +211,36 @@ .dropdown:is(:hover, :focus, :active, :focus-visible, .show) { background-color: transparent !important; - border: 1px solid var(--dropdown-border-color); - color: var(--dropdown-text-color) !important; + border: 1px solid var(--brown-color); + color: var(--brown-color) !important; +} + +.dropdown:is(:focus, :focus-visible) { + outline: 2px solid var(--highlight-color, #a8c7fa); } .dropdownItem { - background-color: white !important; - color: var(--dropdown-text-color) !important; + background-color: var(--bs-white) !important; + color: var(--brown-color) !important; border: none !important; } +.dropdownItem:focus, +.dropdownItem:hover { + outline: 2px solid var(--highlight-color, #a8c7fa); +} + .dropdownItem:hover, .dropdownItem:focus, .dropdownItem:active { - background-color: var(--dropdown-hover-color) !important; - color: var(--dropdown-text-color) !important; + background-color: var(--dropdown-hover-color, #e0e0e0) !important; + color: var(--brown-color) !important; outline: none !important; - box-shadow: none !important; + box-shadow: 0 0 4px var(--highlight-color, #a8c7fa); } .input { - flex: 3; + flex: 1; position: relative; } @@ -106,48 +249,11 @@ margin: 2.5rem 0; align-items: center; gap: 10px; - /* Adjust spacing between items */ } .btnsContainer .btnsBlock { display: flex; -} - -.btnsContainer .btnsBlock button { - margin-left: 1rem; - display: flex; - justify-content: center; - align-items: center; -} - -.btnsContainer .inputContainer { - flex: 1; - position: relative; -} - -.btnsContainer .input { - width: 70%; -} - -.btnsContainer input { - outline: 1px solid var(--bs-gray-400); -} - -.btnsContainer .inputContainer button { - width: 52px; -} - -.listBox { - width: 100%; - flex: 1; -} - -.inputField { - margin-top: 10px; - margin-bottom: 10px; - - background-color: white; - box-shadow: 0 1px 1px var(--input-shadow-color); + width: max-content; } .btnsContainerBlockAndUnblock { @@ -180,14 +286,10 @@ outline: 1px solid var(--bs-gray-400); } -.btnsContainerBlockAndUnblock .inputContainerBlockAndUnblock button { +.btnsContainer .input button { width: 52px; } -.largeBtnsWrapper { - display: flex; -} - .deleteButton { background-color: var(--delete-button-bg); color: var(--delete-button-color); @@ -212,13 +314,13 @@ color: black !important; margin-top: 10px; margin-left: 5px; - border: 1px solid var(--dropdown-border-color); + border: 1px solid var(--brown-color); } .createButton:hover { background-color: var(--grey-bg-color) !important; color: black !important; - border: 1px solid var(--dropdown-border-color) !important; + border: 1px solid var(--brown-color) !important; } .visuallyHidden { @@ -232,9 +334,16 @@ border: 0; } +.inputField { + margin-top: 10px; + margin-bottom: 10px; + background-color: var(--bs-white); + box-shadow: 0 1px 1px var(--input-shadow-color); +} + .inputFieldModal { margin-bottom: 10px; - background-color: white; + background-color: var(--bs-white); box-shadow: 0 1px 1px var(--input-shadow-color); } @@ -257,51 +366,23 @@ } .searchButton:hover { - background-color: var(--search-button-bg); - border-color: var(--search-button-border); -} - -.search { - position: absolute; - z-index: 10; - background-color: var(--search-button-bg); + background-color: var(--search-button-hover-bg, #286fe0); border-color: var(--search-button-border); - bottom: 0; - right: 0; - height: 100%; - display: flex; - justify-content: center; - align-items: center; } -.editButton { - background-color: var(--search-button-bg); - border-color: var(--search-button-border); - color: var(--high-contrast-text); - margin-left: 2; +.searchButton:active { + transform: scale(0.95); } .addButton { margin-bottom: 10px; background-color: var(--search-button-bg); border-color: var(--grey-bg-color); - color: var(--high-contrast-text); } .addButton:hover { background-color: #286fe0; border-color: var(--search-button-border); - /* color: #555555; */ -} - -.modalbtn { - margin-top: 1rem; - display: flex !important; - margin-left: auto; - align-items: center; - background-color: var(--grey-bg-color) !important; - color: black !important; - border: 1px solid var(--dropdown-border-color) !important; } .yesButton { @@ -309,14 +390,14 @@ border-color: var(--search-button-border); } -.mainpageright { - color: var(--dropdown-text-color); +.searchIcon { + color: var(--brown-color); } .infoButton { background-color: var(--search-button-bg); border-color: var(--search-button-border); - color: var(--dropdown-text-color); + color: var(--brown-color); margin-right: 0.5rem; border-radius: 0.25rem; } @@ -342,16 +423,10 @@ margin-top: 20px; } -.mainpageright > hr { - margin-top: 10px; - width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; -} - .rowBackground { background-color: var(--row-background); + max-height: 120px; + overflow-y: auto; } .tableHeader { @@ -360,56 +435,62 @@ font-size: var(--font-size-header); } -.orgUserTagsScrollableDiv { - scrollbar-width: auto; - scrollbar-color: var(--bs-gray-400) var(--bs-white); - - max-height: calc(100vh - 18rem); - overflow: auto; - position: sticky; -} - .errorContainer { min-height: 100vh; } -.errorMessage { - margin-top: 25%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - .errorIcon { transform: scale(1.5); color: var(--bs-danger); margin-bottom: 1rem; + /* Add error icon for non-color indication */ + &::before { + content: '⚠️'; + margin-right: 0.5rem; + } } -.subTagsLink { - color: var(--subtle-blue-grey); - font-weight: 500; - cursor: pointer; +.tag-template-name { + position: absolute; + left: 14.91px; + top: 27.03px; + width: 58.55px; + height: 5.67px; + text-align: center; + font-family: 'Roboto', sans-serif; + font-size: 16px; + letter-spacing: 0; + line-height: 1; + color: #08780b; } .subTagsLink i { visibility: hidden; } -.subTagsLink:hover { +.subTagsLink:hover, +.subTagsLink:focus { color: var(--subtle-blue-grey-hover); font-weight: 600; text-decoration: underline; } -.subTagsLink:hover i { +.subTagsLink:hover i, +.subTagsLink:focus i { visibility: visible; } -.tagsBreadCrumbs { - color: var(--bs-gray); - cursor: pointer; +.manageTagScrollableDiv { + scrollbar-width: thin; + scrollbar-color: var(--bs-gray-400) var(--bs-white); + max-height: calc(100vh - 18rem); + overflow: auto; +} + +.tagsBreadCrumbs:hover { + color: var(--bs-blue); + font-weight: 600; + text-decoration: underline; } .orgUserTagsScrollableDiv { @@ -486,7 +567,7 @@ hr { flex-direction: row; font-weight: 900; font-size: 16px; - color: var(--high-contrast-text); + color: rgb(80, 80, 80); } .rankings { @@ -509,125 +590,300 @@ hr { align-items: center; } -.toggleBtn:hover { - color: var(--bs-primary) !important; +.pageNotFound { + position: relative; + bottom: 20px; } -.custom_table { - border-radius: 20px; - background-color: var(--grey-bg-color); +.pageNotFound h3 { + font-family: 'Roboto', sans-serif; + font-weight: normal; + letter-spacing: 1px; } -.custom_table tbody tr { - background-color: var(--dropdown-hover-color); +.pageNotFound .brand span { + margin-top: 50px; + font-size: 40px; } -.custom_table tbody tr:hover { - background-color: var(--grey-bg-color); - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); +.pageNotFound .brand h3 { + font-weight: 300; + margin: 10px 0 0 0; } -.custom_table tbody tr:focus-within { - outline: 2px solid #000; - outline-offset: -2px; +.pageNotFound h1.head { + font-size: 250px; + font-weight: 900; + color: #31bb6b; + letter-spacing: 25px; + margin: 10px 0 0 0; } -.custom_table tbody td:focus { - outline: 2px solid #000; - outline-offset: -2px; +.pageNotFound h1.head span { + position: relative; + display: inline-block; } - -.listTable { - width: 100%; - box-sizing: border-box; - background: #ffffff; - border: 1px solid #0000001f; - border-radius: 24px; +.pageNotFound h1.head span:before, +.pageNotFound h1.head span:after { + position: absolute; + top: 50%; + width: 50%; + height: 1px; + background: #fff; + content: ''; } - -.listBox .customTable { - margin-bottom: 0%; +.pageNotFound h1.head span:before { + left: -55%; } -.requestsTable thead th { - font-size: 20px; - font-weight: 400; - line-height: 24px; - letter-spacing: 0em; - text-align: left; - color: #000000; - border-bottom: 1px solid #dddddd; - padding: 1.5rem; +.pageNotFound h1.head span:after { + right: -55%; } -.notFound { - flex: 1; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; +.pledgeContainer { + margin: 0.6rem 0; } -.headerBtn { - box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 2px; +.container { + min-height: 100vh; } -.settingsContainer { - min-height: 100vh; +.pledgeModal { + max-width: 80vw; + margin-top: 2vh; + margin-left: 13vw; } -.settingsBody { - min-height: 100vh; - margin: 2.5rem 1rem; +.greenregbtnPledge { + margin-top: 15px; + border: 1px solid var(--bs-gray-300); + box-shadow: 0 2px 2px var(--grey-border-box-color); + padding: 10px 10px; + border-radius: 5px; + background-color: var(--bs-primary); + width: 100%; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; } -.cardHeader { - padding: 1.25rem 1rem 1rem 1rem; - border-bottom: 1px solid var(--bs-gray-200); +.btnsContainerPledge { display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; + gap: 0.8rem; + margin: 2.2rem 0 0.8rem 0; } -.cardHeader .cardTitle { - font-size: 1.2rem; - font-weight: 600; + +.btnsContainerPledge .inputPledge { + flex: 1; + min-width: 18rem; + position: relative; } -.containerBody { - min-height: 180px; - padding-top: 0; - max-height: 570px; - overflow-y: auto; - width: 100%; - max-width: min(400px, 90vw); - scrollbar-width: thin; - scrollbar-color: rgba(0, 0, 0, 0.3) transparent; +.btnsContainerPledge input { + outline: 1px solid var(--bs-gray-400); +} - &::-webkit-scrollbar { - width: thin; - } +.btnsContainerPledge .inputPledge button { + width: 52px; +} - &::-webkit-scrollbar-track { - background: transparent; - } +.inputFieldPledge { + background-color: var(--bs-white); + box-shadow: 0 1px 1px #31bb6b; +} - &::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0.3); - } +.dropdownPledge { + background-color: var(--bs-white); + border: 1px solid var(--bs-primary); + position: relative; + display: inline-block; + color: var(--bs-primary); } -.containerBody .emptyContainer { - display: flex; - min-height: 180px; - justify-content: center; - align-items: center; +.rowBackgroundPledge { + background-color: var(--bs-white); + max-height: 120px; } -.containerBody .rankings { - aspect-ratio: 1; - border-radius: 50%; - width: 35px; +.TableImagePledge { + object-fit: cover; + width: calc(var(--table-image-size) / 2) !important; + height: calc(var(--table-image-size) / 2) !important; + border-radius: 100% !important; +} + +.imageContainerPledge { + display: flex; + align-items: center; + justify-content: center; +} + +.pledgerContainer { + display: flex; + align-items: center; + justify-content: center; + margin: 0.1rem 0.25rem; + gap: 0.25rem; + padding: 0.25rem 0.45rem; + border-radius: 0.35rem; + background-color: var(--bs-primary-rgb, 49, 187, 107, 0.2); + height: 2.2rem; + margin-top: 0.75rem; +} + +.noOutlinePledge input { + outline: none; +} + +.overviewContainer { + display: flex; + gap: 7rem; + width: 100%; + justify-content: space-between; + margin: 1.5rem 0 0 0; + padding: 1.25rem 2rem; + background-color: rgba(255, 255, 255, 0.591); + + box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px; + border-radius: 0.5rem; +} + +.titleContainer { + display: flex; + flex-direction: column; + gap: 0.6rem; +} + +.titleContainer h3 { + font-size: 1.75rem; + font-weight: 750; + color: #5e5e5e; + margin-top: 0.2rem; +} + +.titleContainer span { + font-size: 0.9rem; + margin-left: 0.5rem; + font-weight: lighter; + color: #707070; +} + +.raisedAmount { + display: flex; + justify-content: center; + align-items: center; + font-size: 1.25rem; + font-weight: 750; + color: #5e5e5e; +} + +.progressContainer { + display: flex; + flex-direction: column; + gap: 0.5rem; + flex-grow: 1; +} + +.progress { + margin-top: 0.2rem; + display: flex; + flex-direction: column; + gap: 0.3rem; +} + +.endpoints { + display: flex; + position: relative; + font-size: 0.85rem; +} + +.start { + position: absolute; + top: 0px; +} + +.end { + position: absolute; + top: 0px; + right: 0px; +} + +.moreContainer { + display: flex; + align-items: center; +} + +.moreContainer:hover { + text-decoration: underline; + cursor: pointer; +} + +.popup { + z-index: 50; + border-radius: 0.5rem; + font-family: sans-serif; + font-weight: 500; + font-size: 0.875rem; + margin-top: 0.5rem; + padding: 0.75rem; + border: 1px solid #e2e8f0; + background-color: var(--bs-white); + color: #1e293b; + box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 0.15); + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.popupExtra { + max-height: 15rem; + overflow-y: auto; +} + +.toggleGroupPledge { + width: 50%; + min-width: 27.75rem; + margin: 0.5rem 0rem; +} + +.toggleBtnPledge { + padding: 0rem; + height: 30px; + display: flex; + justify-content: center; + align-items: center; +} + +.toggleBtnPledge:hover { + color: #31bb6b !important; +} + +.card { + width: fit-content; +} + +.cardHeader { + padding: 1.25rem 1rem 1rem 1rem; + border-bottom: 1px solid var(--bs-gray-200); + display: flex; + justify-content: space-between; + align-items: center; +} + +.cardHeader .cardTitle { + font-size: 1.5rem; +} + +.formLabel { + font-weight: normal; + padding-bottom: 0; + font-size: 1rem; + color: black; } .cardBody { @@ -636,130 +892,3163 @@ hr { .cardBody .textBox { margin: 0 0 3rem 0; - color: var(--high-contrast-text); + color: var(--bs-secondary); } -.settingsTabs { - display: none; +.socialInput { + height: 2.5rem; } -@media (min-width: 576px) { - .settingsDropdown { - display: none; - } +.eventContainer { + display: flex; + align-items: start; } -@media (min-width: 576px) { - .settingsTabs { - display: block; - } +.eventDetailsBox { + position: relative; + box-sizing: border-box; + background: #ffffff; + width: 66%; + padding: 0.3rem; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); + border-radius: 20px; + margin-bottom: 0; + margin-top: 20px; +} +.ctacards { + padding: 20px; + width: 100%; + display: flex; + background-color: #ffffff; + margin: 0 4px; + justify-content: space-between; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); + align-items: center; + border-radius: 20px; +} +.ctacards span { + color: rgb(181, 181, 181); + font-size: small; } -@media (max-width: 1020px) { - .btnsContainer { - flex-direction: column; - margin: 1.5rem 0; - } +.justifyspOrganizationEvents { + display: flex; + justify-content: space-between; + margin-top: 20px; +} - .btnsContainer .input { - width: 100%; - } +.titlemodalOrganizationEvents { + color: var(--grey-bg-color-dark); + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid var(--table-bg-color); + width: 65%; +} - .btnsContainer .btnsBlock { - margin: 1.5rem 0 0 0; - justify-content: space-between; - } +.closeButtonOrganizationEvents { + color: var(--delete-button-color); + margin-right: 5px; + background-color: var(--delete-button-bg); + border: white; +} - .btnsContainer .btnsBlock button { - margin: 0; - } +.closeButtonOrganizationEvents:hover { + color: var(--delete-button-bg) !important; + background-color: var(--delete-button-color) !important; + border: white; +} - .btnsContainer .btnsBlock div button { - margin-right: 1.5rem; - } +.datedivOrganizationEvents { + display: flex; + flex-direction: row; + margin-bottom: 15px; } -@media (max-width: 520px) { - .btnsContainer { - margin-bottom: 0; - } +.dateboxOrganizationEvents { + width: 90%; + border-radius: 7px; + border-color: var(--grey-border-box-color); + outline: none; + box-shadow: none; + padding: 2px 5px; + margin-right: 5px; + margin-left: 5px; +} - .btnsContainer .btnsBlock { - display: block; - margin-top: 1rem; - } +.dispflexOrganizationEvents { + display: flex; + align-items: center; + justify-content: space-between; +} - .btnsContainer .btnsBlock div { - flex: 1; - } +.dispflexOrganizationEvents > input { + border: none; + box-shadow: none; + margin-top: 5px; +} - .btnsContainer .btnsBlock div[title='Sort organizations'] { - margin-right: 0.5rem; - } +.checkboxdiv { + display: flex; + margin-bottom: 5px; +} - .btnsContainer .btnsBlock button { - margin-bottom: 1rem; - margin-right: 0; - width: 100%; - } +.checkboxdiv > div { + width: 50%; } -.listBox { +.createButtonOrganizationEvents { + background-color: var(--search-button-bg) !important; + color: black !important; + margin: 15px 0 0; + padding: 10px 10px; + border-radius: 5px; + font-size: 16px; + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; width: 100%; - flex: 1; + border: 1px solid var(--search-button-border); } -.listTable { - width: 100%; +.createButtonOrganizationEvents:hover { + background-color: var(--bs-primary) !important; + color: white !important; + border: 1px solid var(--search-button-border) !important; +} + +.time { + display: flex; + justify-content: space-between; + padding: 15px; + padding-bottom: 0px; + width: 33%; + box-sizing: border-box; background: #ffffff; - border: 1px solid #0000001f; - border-radius: 24px; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); + border-radius: 20px; + margin-bottom: 0; + margin-top: 20px; + margin-left: 10px; } -.listBox .customTable { - margin-bottom: 0%; +.startTime, +.endTime { + display: flex; + font-size: 20px; } -.requestsTable thead th { - font-size: 20px; - font-weight: 400; - line-height: 24px; - letter-spacing: 0em; - text-align: left; - color: #000000; - border-bottom: 1px solid #dddddd; - padding: 1.5rem; +.to { + padding-right: 10px; } -.notFound { - flex: 1; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; +.startDate, +.endDate { + color: #808080; + font-size: 14px; } -@-webkit-keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } +.titlename { + font-weight: 600; + font-size: 25px; + padding: 15px; + padding-bottom: 0px; + width: 50%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } +.description { + color: #737373; + font-weight: 300; + font-size: 14px; + word-wrap: break-word; + padding: 15px; + padding-bottom: 0px; } -@keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); +.toporgloc { + font-size: 16px; + padding: 0.5rem; +} + +.toporgloc span { + color: #737373; +} + +.eventAgendaItemContainer h2 { + margin: 0.6rem 0; +} + +@media (max-width: 768px) { + .btnsContainer { + margin-bottom: 0; + display: flex; + flex-direction: column; } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); + .createAgendaItemButton { + position: absolute; + top: 1rem; + right: 2rem; } } + +.customcell { + background-color: #31bb6b !important; + color: var(--bs-white) !important; + font-size: medium !important; + font-weight: 500 !important; + padding-top: 10px !important; + padding-bottom: 10px !important; +} + +.eventsAttended, +.membername { + color: blue; +} +.actionBtn { + background-color: #ffffff !important; +} +.actionBtn:hover, +.actionBtn:focus, +.actionBtn:active { + color: #39a440 !important; +} + +.table-body > .table-row { + background-color: #fff !important; +} + +.table-body > .table-row:nth-child(2n) { + background: #afffe8 !important; +} + +.organizationFundCampaignContainer { + margin: 0.5rem 0; +} + +.rowBackgroundOrganizationFundCampaign { + background-color: var(--bs-white); + max-height: 120px; +} + +.campaignModal { + max-width: 80vw; + margin-top: 2vh; + margin-left: 13vw; +} + +.greenregbtnOrganizationFundCampaign { + margin: 1rem 0 0; + margin-top: 15px; + border: 1px solid var(--bs-gray-300); + box-shadow: 0 2px 2px var(--grey-border-box-color); + padding: 10px 10px; + border-radius: 5px; + background-color: var(--bs-primary); + width: 100%; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + flex: 1; +} + +.goalButtonOrganizationFundCampaign { + border: 1px solid var(--grey-border-box-color) !important; + color: #707070 !important; + width: 75%; + padding: 10px; + border-radius: 8px; + display: block; + margin: auto; + box-shadow: 5px 5px 4px 0px rgba(49, 187, 107, 0.12); +} + +.redregbtn { + margin: 1rem 0 0; + margin-top: 15px; + border: 1px solid var(--grey-border-box-color); + box-shadow: 0 2px 2px var(--grey-border-box-color); + padding: 10px 10px; + border-radius: 5px; + width: 100%; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + width: 100%; + flex: 1; +} + +.campaignNameInfo { + font-size: medium; + cursor: pointer; +} +.campaignNameInfo:hover { + color: blue; + transform: translateY(-2px); +} + +.inputFieldOrganizationFundCampaign { + background-color: var(--bs-white); + box-shadow: 0 1px 1px var(--search-button-bg); +} + +.dropdownOrganizationFundCampaign { + background-color: var(--bs-white); + border: 1px solid var(--grey-border-box-color); + position: relative; + display: inline-block; + color: #707070; +} + +.btnsContainerOrganizationFundCampaign { + display: flex; + margin: 2rem 0 2rem 0; + gap: 0.8rem; +} + +.btnsContainerOrganizationFundCampaign .btnsBlockOrganizationFundCampaign { + display: flex; + gap: 0.8rem; +} + +.btnsContainerOrganizationFundCampaign + .btnsBlockOrganizationFundCampaign + div + button { + display: flex; + margin-left: 1rem; + justify-content: center; + align-items: center; +} + +.btnsContainerOrganizationFundCampaign .inputOrganizationFundCampaign { + flex: 1; + position: relative; +} + +.btnsContainerOrganizationFundCampaign input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainerOrganizationFundCampaign .inputOrganizationFundCampaign button { + width: 52px; +} + +@media (max-width: 1020px) { + .btnsContainerOrganizationFundCampaign { + flex-direction: column; + margin: 1.5rem 0; + } + + .btnsContainerOrganizationFundCampaign .btnsBlockOrganizationFundCampaign { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainerOrganizationFundCampaign + .btnsBlockOrganizationFundCampaign + div + button { + margin: 0; + } + + .createFundBtn { + margin-top: 0; + } +} + +@media screen and (max-width: 575.5px) { + .mainpageright { + width: 98%; + } +} + +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainerOrganizationFundCampaign { + margin-bottom: 0; + } + + .btnsContainerOrganizationFundCampaign .btnsBlockOrganizationFundCampaign { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainerOrganizationFundCampaign + .btnsBlockOrganizationFundCampaign + div + button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} + +@media (max-width: 1024px) { + .pageNotFound h1.head { + font-size: 200px; + letter-spacing: 25px; + } +} + +@media (max-width: 768px) { + .pageNotFound h1.head { + font-size: 150px; + letter-spacing: 25px; + } +} + +@media (max-width: 640px) { + .pageNotFound h1.head { + font-size: 150px; + letter-spacing: 0; + } +} + +@media (max-width: 480px) { + .pageNotFound .brand h3 { + font-size: 20px; + } + .pageNotFound h1.head { + font-size: 130px; + letter-spacing: 0; + } + .pageNotFound h1.head span:before, + .pageNotFound h1.head span:after { + width: 40%; + } + .pageNotFound h1.head span:before { + left: -45%; + } + .pageNotFound h1.head span:after { + right: -45%; + } + .pageNotFound p { + font-size: 18px; + } +} + +@media (max-width: 320px) { + .pageNotFound .brand h3 { + font-size: 16px; + } + .pageNotFound h1.head { + font-size: 100px; + letter-spacing: 0; + } + .pageNotFound h1.head span:before, + .pageNotFound h1.head span:after { + width: 25%; + } + .pageNotFound h1.head span:before { + left: -30%; + } + .pageNotFound h1.head span:after { + right: -30%; + } +} +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; + } + + .btn { + flex-direction: column; + justify-content: center; + } + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainer .btnsBlock div { + flex: 1; + } + + .btnsContainer .btnsBlock div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} + +@media (max-width: 1120px) { + .contract { + padding-left: calc(250px + 2rem + 1.5rem); + } + + .listBox .itemCard { + width: 100%; + } +} + +@media (max-width: 1020px) { + .btnsContainer { + flex-direction: column; + margin: 1.5rem 0; + } + + .btnsContainer .btnsBlock { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainer .btnsBlock button { + margin: 0; + } + + .btnsContainer .btnsBlock div button { + margin-right: 1.5rem; + } +} + +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +.btnsContainer .input { + flex: 1; + position: relative; + max-width: 60%; + justify-content: space-between; +} + +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); +} + +.list_box { + height: auto; + overflow-y: auto; + width: 100%; +} + +.fundName { + font-weight: 600; + cursor: pointer; +} + +.modalHeader { + border: none; + padding-bottom: 0; +} + +.label { + color: var(--bs-emphasis-color); +} + +.fundModal { + max-width: 80vw; + margin-top: 2vh; + margin-left: 13vw; +} + +.btnsContainer .btnsBlock button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.errorMessage { + margin-top: 25%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.tableHeaders { + background-color: var(--bs-primary-text-emphasis); + color: var(--bs-white); + font-size: 1rem; +} + +.rowBackgrounds { + background-color: var(--bs-white); + max-height: 120px; + overflow-y: auto; /* Handle content overflow */ +} + +.subTagsLink { + color: var(--bs-blue); + font-weight: 500; + cursor: pointer; + /* Prevent layout shift */ + &::after { + display: block; + content: attr(data-text); + font-weight: 600; + height: 0; + overflow: hidden; + visibility: hidden; + } +} + +.tagsBreadCrumbs { + color: var(--bs-gray); + cursor: pointer; + /* Prevent layout shift */ + &::after { + display: block; + content: attr(data-text); + font-weight: 600; + height: 0; + overflow: hidden; + visibility: hidden; + } +} + +.tagsBreadCrumbs:hover, +.tagsBreadCrumbs:focus { + color: var(--bs-blue); + font-weight: 600; + text-decoration: underline; +} + +.subTagsScrollableDiv { + scrollbar-width: auto; + scrollbar-color: var(--bs-gray-400) var(--bs-white); + max-height: calc(100vh - 18rem); + overflow: auto; +} + +#individualRadio, +#requestsRadio, +#groupsRadio, +.toggleBtn:hover { + color: var(--brand-primary) !important; +} + +.toggleBtn:hover { + color: #31bb6b !important; +} + +input[type='radio']:checked + label { + background-color: var(--brand-primary-light) !important; +} + +input[type='radio']:checked + label:hover { + color: black !important; +} + +.dropdownToggle { + margin-bottom: 0; + display: flex; +} + +.dropdownModalToggle { + width: 50%; +} + +.greenregbtn { + margin-top: 1rem; + border: 1px solid var(--bs-gray-300); + box-shadow: 0 2px 2px var(--bs-gray-300); + padding: 10px 10px; + border-radius: 5px; + background-color: var(--bs-primary); + width: 100%; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + width: 100%; +} + +.manageBtn { + margin: 1rem 0 0; + margin-top: 15px; + border: 1px solid var(--grey-border-box-color); + box-shadow: 0 2px 2px var(--grey-border-box-color); + padding: 10px 10px; + border-radius: 5px; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + width: 45%; + transition: + transform 0.2s, + box-shadow 0.2s; +} + +.removeFilterIcon { + cursor: pointer; +} + +.searchForm { + display: inline; +} + +.view { + margin-left: 2%; + font-weight: 600; + font-size: 16px; + color: var(--bs-gray-600); +} + +/* header (search, filter, dropdown) */ +.btncon .btnsContainer { + display: flex; + margin: 0.5rem 0 1.5rem 0; +} + +.btncon .btnsContainer .input { + flex: 1; + min-width: 18rem; + position: relative; +} + +.btncon .btnsContainer input { + outline: 1px solid var(--bs-gray-400); +} + +.btncon .btnsContainer .input button { + width: 52px; +} + +.noOutline input { + outline: none; +} + +.noOutline input:disabled { + -webkit-text-fill-color: black !important; +} + +.noOutline textarea:disabled { + color: var(--bs-black) !important; + opacity: 1; +} + +.inputFields { + box-shadow: 0 1px 1px var(--brand-primary); +} + +.dropdowns { + background-color: var(--bs-white); + border: 1px solid #31bb6b; + position: relative; + display: inline-block; + color: #31bb6b; +} + +.chipIcon { + height: 0.9rem !important; +} + +.chip { + height: 1.5rem !important; +} + +.active { + background-color: var(--status-active-bg); +} + +.pending { + background-color: var(--status-pending-bg); + color: var(--status-pending-text); + border-color: var(--status-pending-border); +} + +/* Modals */ +.itemModal { + max-width: 80vw; + margin-top: 2vh; + margin-left: 13vw; +} + +.titlemodal { + color: #707070; + font-weight: 600; + font-size: 32px; + width: 65%; + margin-bottom: 0px; +} + +.modalCloseBtn { + width: 40px; + height: 40px; + padding: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.imageContainer { + display: flex; + align-items: center; + justify-content: center; + margin-right: 0.5rem; +} + +.TableImages { + object-fit: cover; + width: var(--image-width, 100%); + height: var(--image-height, auto); + border-radius: 0; + margin-right: var(--image-spacing, 8px); +} + +.avatarContainer { + width: 28px; + height: 26px; +} + +/* Modal Table (Groups & Assignments) */ +.modalTable { + max-height: 220px; + overflow-y: auto; +} + +/* * Refortoring css for OrgList */ + +.btnsContainerOrgList { + display: flex; + margin: 2.5rem 0 2.5rem 0; +} + +.btnsContainerOrgList .btnsBlockOrgList { + display: flex; +} + +.orgCreationBtn { + width: 100%; + border: None; +} + +.enableEverythingBtn { + width: 100%; + border: None; +} + +.pluginStoreBtn { + width: 100%; + background-color: var(--bs-white); + color: var(--brown-color); + border: 0.5px solid var(--brown-color); +} +.searchButtonOrgList { + position: absolute; + z-index: 10; + bottom: 0; + inset-inline-end: 0px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.pluginStoreBtn:hover, +.pluginStoreBtn:focus { + background-color: var(--dropdown-hover-color) !important; + color: var(--brown-color) !important; + border-color: var(--brown-color) !important; +} + +.flexContainer { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +.orText { + display: block; + position: absolute; + top: -0.2rem; + left: calc(50% - 2.6rem); + margin: 0 auto; + padding: 0.5rem 2rem; + z-index: 100; + background: var(--bs-white); + color: var(--bs-secondary); +} + +.sampleOrgSection { + display: grid; + grid-template-columns: repeat(1, 1fr); + row-gap: 1em; + width: 100%; +} + +.sampleOrgCreationBtn { + width: 100%; + background-color: transparent; + color: #707070; + border-color: #707070; + display: flex; + justify-content: center; + align-items: center; +} + +.sampleHover:hover { + border-color: grey; + color: grey; +} + +.sampleModalTitle { + background-color: var(--bs-primary); +} + +.btnsContainerOrgList .btnsBlockOrgList button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.btnsContainerOrgList .inputOrgList { + flex: 1; + position: relative; +} + +.btnsContainerOrgList input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainerOrgList .inputOrgList button { + width: 52px; +} + +.listBoxOrgList { + display: flex; + flex-wrap: wrap; + overflow: unset !important; +} + +.listBoxOrgList .itemCardOrgList { + width: 50%; +} +.notFound { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.mainpage { + display: flex; + flex-direction: row; +} + +.editIcon { + position: absolute; + top: 10px; + left: 20px; + cursor: pointer; +} + +.selectWrapper { + position: relative; +} + +.selectWithChevron { + appearance: none; + padding-right: 30px; +} + +.selectWrapper::after { + content: '\25BC'; + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + pointer-events: none; +} + +.sidebarstickyMemberDetail { + padding: 0 2rem; + text-overflow: ellipsis; + /* overflow-x: hidden; */ +} + +.sidebarstickyMemberDetail > p { + margin-top: -10px; +} + +.navitem { + padding-left: 27%; + padding-top: 12px; + padding-bottom: 12px; + cursor: pointer; +} + +.searchtitleMemberDetail { + color: #707070; + font-weight: 600; + font-size: 18px; + margin-top: 60px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #eaebef; + width: 60%; +} + +.contact { + width: 100%; +} + +.sidebarstickyMemberDetail > input { + text-decoration: none; + margin-bottom: 50px; + border-color: var(--grey-border-box-color); + width: 80%; + border-radius: 7px; + padding-top: 5px; + padding-bottom: 5px; + padding-right: 10px; + padding-left: 10px; + box-shadow: none; +} + +.logintitleMemberDetail { + color: #707070; + font-weight: 600; + font-size: 20px; + margin-bottom: 30px; + padding-bottom: 5px; + border-bottom: 3px solid #eaebef; + width: 30%; +} + +.logintitleadmin { + color: #707070; + font-weight: 600; + font-size: 20px; + margin-top: 50px; + margin-bottom: 40px; + padding-bottom: 5px; + border-bottom: 3px solid #eaebef; + width: 60%; +} + +.cardBodyMemberDetail { + height: 35vh; + overflow-y: scroll; +} + +.justifyspMemberDetail { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + /* gap : 2px; */ +} + +.flexclm { + display: flex; + flex-direction: column; +} + +.btngroup { + display: flex; + gap: 2rem; + margin-bottom: 2rem; +} + +@media screen and (max-width: 1200px) { + .justifyspMemberDetail { + padding-left: 55px; + display: flex; + justify-content: space-evenly; + } + + .mainpageright { + width: 100%; + } + + .invitebtn { + position: relative; + right: 15px; + } +} + +.invitebtn { + border: 1px solid var(--grey-border-box-color); + box-shadow: 0 2px 2px var(--grey-border-box-color); + border-radius: 5px; + font-size: 16px; + height: 60%; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + background-color: #eaebef; + margin-right: 13px; +} + +.flexdir { + display: flex; + flex-direction: row; + justify-content: space-between; + border: none; +} + +.form_wrapper { + margin-top: 27px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + position: absolute; + display: flex; + flex-direction: column; + width: 30%; + padding: 40px 30px; + background: #ffffff; + border-color: var(--grey-border-box-color); + border-width: 5px; + border-radius: 10px; + max-height: 86vh; + overflow: auto; +} + +.form_wrapper form { + display: flex; + align-items: left; + justify-content: left; + flex-direction: column; +} + +.titlemodalMemberDetail { + color: #707070; + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #eaebef; + width: 65%; +} + +.checkboxdiv > label { + margin-right: 50px; +} + +.checkboxdiv > label > input { + margin-left: 10px; +} + +.orgphoto { + margin-top: 5px; +} + +.orgphoto > input { + margin-top: 10px; + cursor: pointer; + margin-bottom: 5px; +} + +.cancel > i { + margin-top: 5px; + transform: scale(1.2); + cursor: pointer; + color: #707070; +} + +.greenregbtnMemberDetail { + margin: 1rem 0 0; + margin-top: 10px; + border: 1px solid var(--grey-border-box-color); + box-shadow: 0 2px 2px var(--grey-border-box-color); + padding: 10px 10px; + border-radius: 5px; + background-color: #eaebef; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; +} + +.whiteregbtn { + margin: 1rem 0 0; + margin-right: 2px; + margin-top: 10px; + border: 1px solid #eaebef; + padding: 10px 10px; + border-radius: 5px; + background-color: var(--bs-white); + font-size: 16px; + color: #707070; + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; +} + +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.list_boxMemberDetail { + height: 70vh; + overflow-y: auto; + width: auto; + padding-right: 50px; +} + +.dispflex { + display: flex; +} + +.dispflex > input { + width: 20%; + border: none; + box-shadow: none; + margin-top: 5px; +} + +.checkboxdivMemberDetail { + display: flex; +} + +.checkboxdivMemberDetail > div { + width: 50%; +} + +@media only screen and (max-width: 600px) { + .sidebar { + position: relative; + bottom: 18px; + } + + .invitebtn { + width: 135px; + position: relative; + right: 10px; + } + + .form_wrapper { + width: 90%; + } + + .searchtitleMemberDetail { + margin-top: 30px; + } +} + +/* User page */ + +.memberfontcreatedbtn { + border-radius: 7px; + border-color: var(--grey-border-box-color); + background-color: #eaebef; + color: var(--bs-white); + box-shadow: none; + height: 2.5rem; + width: max-content; + display: flex; + justify-content: center; + align-items: center; +} + +.userImage { + width: 180px; + height: 180px; + object-fit: cover; + border-radius: 8px; +} + +@media only screen and (max-width: 1200px) { + .userImage { + width: 100px; + height: 100px; + } +} + +.activeBtn { + width: 100%; + display: flex; + color: #fff; + border: 1px solid #000; + background-color: #eaebef; + transition: 0.5s; +} + +.activeBtn:hover { + color: #fff; + background: #23864c; + transition: 0.5s; +} + +.inactiveBtn { + width: 100%; + display: flex; + color: #707070; + border: 1px solid #31bb6a60; + background-color: #fff; + transition: 0.5s; +} + +.inactiveBtn:hover { + color: #fff; + background: #eaebef; + transition: 0.5s; +} + +.sidebarstickyMemberDetail > button { + display: flex; + align-items: center; + text-align: start; + padding: 0 1.5rem; + height: 3.25rem; + margin: 0 0 1.5rem 0; + font-weight: bold; + border-radius: 50px; +} + +.bgFill { + height: 2rem; + width: 2rem; + border-radius: 50%; + margin-right: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.activeBtn .bgFill { + background-color: #fff; +} + +.activeBtn i { + color: #707070; +} + +.inactiveBtn .bgFill { + background-color: #eaebef; +} + +.inactiveBtn:hover .bgFill { + background-color: #fff; +} + +.inactiveBtn i { + color: #fff; +} + +.inactiveBtn:hover i { + color: #707070; +} + +.topRadius { + border-top-left-radius: 16px; + border-top-right-radius: 16px; +} + +.titlenameMemberDetail { + font-weight: 600; + font-size: 25px; + padding: 15px; + padding-bottom: 0px; + width: 50%; +} + +.toporglocMemberDetail { + font-size: 16px; + padding: 15px; + padding-bottom: 0px; +} + +.toporglocMemberDetail span { + color: #737373; +} + +.inputColor { + background: #f1f3f6; +} + +.cardHeaderMemberDetail { + display: flex; + justify-content: space-between; + align-items: center; +} + +.width60 { + width: 60%; +} + +.maxWidth40 { + max-width: 40%; +} +.maxWidth50 { + max-width: 50%; +} + +.allRound { + border-radius: 16px; +} + +.WidthFit { + width: fit-content; +} + +.dateboxMemberDetail { + border-radius: 7px; + border-color: var(--grey-border-box-color); + outline: none; + box-shadow: none; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 5px; + padding-left: 5px; + margin-right: 5px; + margin-left: 5px; +} + +.dateboxMemberDetail > div > input { + padding: 0.5rem 0 0.5rem 0.5rem !important; /* top, right, bottom, left */ + background-color: #f1f3f6; + border-radius: var(--bs-border-radius) !important; + border: none !important; +} + +.dateboxMemberDetail > div > div { + margin-left: 0px !important; +} + +.dateboxMemberDetail > div > fieldset { + border: none !important; + /* background-color: #f1f3f6; */ + border-radius: var(--bs-border-radius) !important; +} + +.dateboxMemberDetail > div { + margin: 0.5rem !important; + background-color: #f1f3f6; +} + +input::file-selector-button { + background-color: black; + color: var(--bs-white); +} + +.Outline { + outline: 1px solid var(--bs-gray-400); +} + +.tagLink { + font-weight: 600; + color: var(--bs-gray-700); + cursor: pointer; +} + +.tagLink:hover { + font-weight: 800; + color: var(--bs-blue); + text-decoration: underline; +} + +@media (max-width: 1440px) { + .contractOrgList { + padding-left: calc(250px + 2rem + 1.5rem); + } + + .listBoxOrgList .itemCardOrgList { + width: 100%; + } +} + +@media (max-width: 1020px) { + .btnsContainerOrgList { + flex-direction: column; + margin: 1.5rem 0; + } + + .btnsContainerOrgList .btnsBlockOrgList { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainerOrgList .btnsBlockOrgList button { + margin: 0; + } + + .btnsContainerOrgList .btnsBlockOrgList div button { + margin-right: 1.5rem; + } +} + +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainerOrgList { + margin-bottom: 0; + } + + .btnsContainerOrgList .btnsBlockOrgList { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainerOrgList .btnsBlockOrgList div { + flex: 1; + } + + .btnsContainerOrgList .btnsBlockOrgList div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainerOrgList .btnsBlockOrgList button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} +@media (max-width: 600px) { + .cardBody { + min-height: 120px; + } + + .cardBody .iconWrapper { + position: absolute; + top: 1rem; + left: 1rem; + } + + .cardBody .textWrapper { + margin-top: calc(0.5rem + 36px); + text-align: right; + } + + .cardBody .textWrapper .primaryText { + font-size: 1.5rem; + } + + .cardBody .textWrapper .secondaryText { + font-size: 1rem; + } +} + +.cardBody { + padding: 1.25rem 1.5rem; +} + +.cardBody .iconWrapper { + position: relative; + height: 48px; + width: 48px; + display: flex; + justify-content: center; + align-items: center; +} + +.cardBody .iconWrapper .themeOverlay { + background: var(--bs-primary); + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.12; + border-radius: 50%; +} + +.cardBody .textWrapper .primaryText { + font-size: 24px; + font-weight: bold; + display: block; +} + +.cardBody .textWrapper .secondaryText { + font-size: 14px; + display: block; + color: var(--bs-secondary); +} + +/* Loading OrgList CSS */ + +.itemCardOrgList .loadingWrapper { + background-color: var(--bs-white); + margin: 0.5rem; + height: calc(120px + 2rem); + padding: 1rem; + border-radius: 8px; + outline: 1px solid var(--bs-gray-200); + position: relative; +} + +.itemCardOrgList .loadingWrapper .innerContainer { + display: flex; +} + +.itemCardOrgList .loadingWrapper .innerContainer .orgImgContainer { + width: 120px; + height: 120px; + border-radius: 4px; +} + +.itemCardOrgList .loadingWrapper .innerContainer .content { + flex: 1; + display: flex; + flex-direction: column; + margin-left: 1rem; +} + +.titlemodaldialog { + color: #707070; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; +} + +form label { + font-weight: bold; + padding-bottom: 1px; + font-size: 14px; + color: #707070; +} + +form > input { + display: block; + margin-bottom: 20px; + border: 1px solid var(--grey-border-box-color); + box-shadow: 2px 1px var(--grey-border-box-color); + padding: 10px 20px; + border-radius: 5px; + background: none; + width: 100%; + transition: all 0.3s ease-in-out; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; +} + +.itemCardOrgList .loadingWrapper .innerContainer .content h5 { + height: 24px; + width: 60%; + margin-bottom: 0.8rem; +} + +.modalbody { + width: 50px; +} + +.pluginStoreBtnContainer { + display: flex; + gap: 1rem; +} + +.itemCardOrgList .loadingWrapper .innerContainer .content h6[title='Location'] { + display: block; + width: 45%; + height: 18px; +} + +.itemCardOrgList .loadingWrapper .innerContainer .content h6 { + display: block; + width: 30%; + height: 16px; + margin-bottom: 0.8rem; +} + +.itemCardOrgList .loadingWrapper .button { + position: absolute; + height: 48px; + width: 92px; + bottom: 1rem; + right: 1rem; + z-index: 1; +} + +.login_background { + min-height: 100vh; +} + +.communityLogo { + object-fit: contain; +} + +.row .left_portion { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: 100vh; +} + +.selectOrgText input { + outline: none !important; +} + +.row .left_portion .inner .palisadoes_logo { + width: 600px; + height: auto; +} + +.row .right_portion { + min-height: 100vh; + position: relative; + overflow-y: scroll; + display: flex; + flex-direction: column; + justify-content: center; + padding: 1rem 2.5rem; + background: var(--bs-white); +} + +.row .right_portion::-webkit-scrollbar { + width: 8px; +} + +.row .right_portion::-webkit-scrollbar-track { + background: transparent; +} + +.row .right_portion::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +.row .right_portion .langChangeBtn { + margin: 0; + position: absolute; + top: 1rem; + left: 1rem; +} + +.langChangeBtnStyle { + width: 7.5rem; + height: 2.2rem; + padding: 0; +} + +.talawa_logo { + height: clamp(3rem, 8vw, 5rem); + width: auto; + aspect-ratio: 1; + display: block; + margin: 1.5rem auto 1rem; + @media (prefers-reduced-motion: no-preference) { + -webkit-animation: zoomIn 0.3s ease-in-out; + animation: zoomIn 0.3s ease-in-out; + } +} + +.row .orText { + display: block; + position: absolute; + top: 0; + left: calc(50% - 2.6rem); + margin: 0 auto; + padding: 0.35rem 2rem; + z-index: 100; + background: var(--bs-white); + color: var(--bs-secondary); +} + +.email_button { + position: absolute; + z-index: 10; + bottom: 0; + right: 0; + height: 100%; + display: flex; + background-color: var(--search-button-bg); + border-color: var(--search-button-border); + justify-content: center; + align-items: center; +} + +.login_btn { + background-color: var(--search-button-bg); + border-color: var(--search-button-border); + margin-top: 1rem; + margin-bottom: 1rem; + width: 100%; + transition: background-color 0.2s ease; + cursor: pointer; +} + +.reg_btn { + background-color: var(--dropdown-border-color); + border-color: var(--dropdown-border-color); + margin-top: 1rem; + color: var(--bs-white); + margin-bottom: 1rem; + width: 100%; + transition: background-color 0.2s ease; + cursor: pointer; +} + +.active_tab { + -webkit-animation: fadeIn 0.3s ease-in-out; + animation: fadeIn 0.3s ease-in-out; +} + +.socialIcons { + display: flex; + gap: 16px; + justify-content: center; +} + +.password_checks { + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-direction: column; + gap: var(--spacing-md, 1rem); +} + +.password_check_element { + padding: var(--spacing-sm, 0.5rem) 0; +} + +.password_check_element_top { + margin-top: var(--spacing-lg, 1.125rem); +} + +.password_check_element_bottom { + margin-bottom: var(--spacing-lg, 1.25rem); +} + +@media (max-width: 450px) { + .itemCardOrgList .loadingWrapper { + height: unset; + margin: 0.5rem 0; + padding: 1.25rem 1.5rem; + } + + .itemCardOrgList .loadingWrapper .innerContainer { + flex-direction: column; + } + + .itemCardOrgList .loadingWrapper .innerContainer .orgImgContainer { + height: 200px; + width: 100%; + margin-bottom: 0.8rem; + } + + .itemCardOrgList .loadingWrapper .innerContainer .content { + margin-left: 0; + } + + .itemCardOrgList .loadingWrapper .button { + bottom: 0; + right: 0; + border-radius: 0.5rem; + position: relative; + margin-left: auto; + display: block; + } +} + +/* * Refortoring css for Leaderboard */ + +.TableImageSmall { + object-fit: cover; + width: var(--table-image-small-size); + height: var(--table-image-small-size); + border-radius: 100%; +} + +.avatarContainer { + width: 28px; + height: 26px; +} + +.imageContainer { + display: flex; + align-items: center; + justify-content: center; + margin-right: 0.5rem; +} +/* CSS Refactor for OrgPost */ + +.btnsContainerOrgPost { + display: flex; + margin: 2.5rem 0 2.5rem 0; +} + +.btnsContainerOrgPost .btnsBlockOrgPost { + display: flex; +} + +.btnsContainerOrgPost .btnsBlockOrgPost button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.btnsContainerOrgPost .inputOrgPost { + flex: 1; + position: relative; +} + +.btnsContainerOrgPost input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainerOrgPost .inputOrgPost button { + width: 52px; +} + +.previewOrgPost { + display: flex; + position: relative; + width: 100%; + margin-top: 10px; + justify-content: center; +} +.previewOrgPost img { + width: 400px; + height: auto; +} +.previewOrgPost video { + width: 400px; + height: auto; +} +.mainpagerightOrgPost > hr { + margin-top: 20px; + width: 100%; + margin-left: -15px; + margin-right: -15px; + margin-bottom: 20px; +} + +.pageWrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; +} + +.cardTemplate { + padding: 2rem; + background-color: #fff; + border-radius: 0.8rem; + border: 1px solid var(--bs-gray-200); +} + +.keyWrapper { + height: 72px; + width: 72px; + transform: rotate(180deg); + transform-origin: center; + position: relative; + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + margin: 1rem auto; +} + +.keyWrapper .themeOverlay { + position: absolute; + background-color: var(--bs-primary); + height: 100%; + width: 100%; + opacity: var(--theme-overlay-opacity, 0.15); +} + +.keyWrapper .keyLogo { + height: 42px; + width: 42px; + -webkit-animation: zoomIn 0.3s ease-in-out; + animation: zoomIn 0.3s ease-in-out; +} + +@media (max-width: 1020px) { + .btnsContainerOrgPost { + flex-direction: column; + margin: 1.5rem 0; + } + + .btnsContainerOrgPost .btnsBlockOrgPost { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainerOrgPost .btnsBlockOrgPost button { + margin: 0; + } + + .btnsContainerOrgPost .btnsBlockOrgPost div button { + margin-right: 1.5rem; + } +} + +@media (max-width: 992px) { + .row .left_portion { + padding: 0 2rem; + } + .row .left_portion .inner .palisadoes_logo { + width: 100%; + } +} + +@media (max-width: 769px) { + .row { + flex-direction: column-reverse; + } + .row .right_portion, + .row .left_portion { + height: unset; + } + .row .right_portion { + min-height: 100vh; + overflow-y: unset; + } + .row .left_portion .inner { + display: flex; + justify-content: center; + } + .row .left_portion .inner .palisadoes_logo { + height: 70px; + width: unset; + position: absolute; + margin: 0.5rem; + top: 0; + right: 0; + z-index: 100; + } + .row .left_portion .inner p { + margin-bottom: 0; + padding: 1rem; + } + .socialIcons { + margin-bottom: 1rem; + } +} + +@media (max-width: 577px) { + .row .right_portion { + padding: 1rem 1rem 0 1rem; + } + .row .right_portion .langChangeBtn { + position: absolute; + margin: 1rem; + left: 0; + top: 0; + } + .marginTopForReg { + margin-top: 4rem !important; + } + .row .right_portion .talawa_logo { + height: 120px; + margin: 0 auto 2rem auto; + } + .socialIcons { + margin-bottom: 1rem; + } +} + +@media (prefers-reduced-motion: reduce) { + .talawa_logo { + animation: none; + } + + .active_tab { + animation: none; + } +} + +@-webkit-keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@-webkit-keyframes fadeIn { + 0% { + opacity: 0; + -webkit-transform: translateY(2rem); + transform: translateY(2rem); + } + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + -webkit-transform: translateY(2rem); + transform: translateY(2rem); + } + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainerOrgPost { + margin-bottom: 0; + } + + .btnsContainerOrgPost .btnsBlockOrgPost { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainerOrgPost .btnsBlockOrgPost div { + flex: 1; + } + + .btnsContainerOrgPost .btnsBlockOrgPost div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainerOrgPost .btnsBlockOrgPost button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} +@media screen and (max-width: 575.5px) { + .mainpagerightOrgPost { + width: 98%; + } +} +.addbtnOrgPost { + border: 1px solid var(--grey-border-box-color); + box-shadow: 0 2px 2px var(--grey-border-box-color); + border-radius: 5px; + font-size: 16px; + height: 60%; + width: 60%; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; +} +.postinfo { + height: 80px; + margin-bottom: 20px; +} +.closeButtonOrgPost { + position: absolute; + top: 0px; + right: 0px; + background: transparent; + transform: scale(1.2); + cursor: pointer; + border: none; + color: #707070; + font-weight: 600; + font-size: 16px; +} +button[data-testid='createPostBtn'] { + display: block; +} +.loader, +.loader:after { + border-radius: 50%; + width: var(--loader-size); + height: var(--loader-size); +} +.loader { + margin: 60px auto; + margin-top: 35vh !important; + font-size: 10px; + position: relative; + text-indent: -9999em; + border-top: 1.1em solid rgba(255, 255, 255, 0.2); + border-right: 1.1em solid rgba(255, 255, 255, 0.2); + border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); + border-left: 1.1em solid #febc59; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation: load8 1.1s infinite linear; + animation: load8 1.1s infinite linear; +} +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +.list_box { + height: 70vh; + overflow-y: auto; + width: auto; +} +@media only screen and (max-width: 600px) { + .form_wrapper { + width: 90%; + top: 45%; + } +} +.cardItem { + position: relative; + display: flex; + align-items: center; + border: 1px solid var(--bs-gray-200); + border-radius: 8px; + margin-top: 20px; +} +@-webkit-keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +.cardItem .iconWrapper { + position: relative; + height: 40px; + width: 40px; + display: flex; + justify-content: center; + align-items: center; +} + +.cardItem .iconWrapper .themeOverlay { + background: var(--bs-primary); + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.12; + border-radius: 50%; +} + +.cardItem .title { + font-size: 1rem; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; /* Fallback for browsers that don't support line-clamp */ + margin-left: 3px; +} +/* Modern browsers - enable line clamping */ +@supports (-webkit-line-clamp: 1) { + .cardItem .title, + .cardItem .location, + .cardItem .time { + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + white-space: initial; + } +} +.cardItem .location { + font-size: 0.9rem; + color: var(--bs-primary); + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 1; + line-clamp: 1; + -webkit-box-orient: vertical; +} + +.cardItem .time { + font-size: 0.9rem; + color: var(--bs-secondary); +} + +.cardItem .creator { + font-size: 1rem; + color: var(--bs-success, #21d015); +} +.rightCard { + display: flex; + gap: 7px; + min-width: 170px; + justify-content: center; + flex-direction: column; + margin-left: 10px; + overflow-x: hidden; + width: 210px; + + @keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } + } + + /* AddOnEntry.tsx */ + + .entrytoggle { + margin: 24px 24px 0 auto; + width: fit-content; + } + + .entryaction { + margin-left: auto; + display: flex !important; + align-items: center; + background-color: transparent; + color: #31bb6b; + } + + .entryaction .spinner-grow { + height: 1rem; + width: 1rem; + margin-right: 8px; + } +} + +.leftDrawer { + width: calc(300px + 2rem); + position: fixed; + top: 0; + bottom: 0; + z-index: 100; + display: flex; + flex-direction: column; + padding: 1rem 1rem 0 1rem; + background-color: #f6f8fc; + transition: 0.5s; + font-family: var(--bs-leftDrawer-font-family); +} + +.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 .talawaLogo { + width: 100%; + height: 65px; +} + +.leftDrawer .talawaText { + font-size: 20px; + text-align: center; + font-weight: 500; +} + +.leftDrawer .titleHeader { + margin: 2rem 0 1rem 0; + font-weight: 600; +} + +.leftDrawer .optionList button { + display: flex; + align-items: center; + width: 100%; + text-align: start; + margin-bottom: 0.8rem; + border-radius: 16px; + outline: none; + border: none; +} + +.leftDrawer .optionList button .iconWrapper { + width: 36px; +} + +.leftDrawer .profileContainer { + border: none; + width: 100%; + padding: 2.1rem 0.5rem; + height: 52px; + display: flex; + align-items: center; + background-color: var(--bs-white); +} + +.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; + } + + .inactiveDrawer { + opacity: 0; + left: 0; + z-index: -1; + animation: closeDrawer 0.4s ease-in-out; + } + + .activeDrawer { + display: flex; + z-index: 100; + animation: openDrawer 0.6s ease-in-out; + } + + .logout { + margin-bottom: 2.5rem !important; + } +} + +@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; + } +} + +/* CustomRecurrenceModal.tsx */ + +.titlemodalCustomRecurrenceModal { + color: var(--grey-bg-color-dark); + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid var(--subtle-blue-grey); + width: 65%; +} + +.recurrenceRuleNumberInput { + width: 70px; +} + +.recurrenceRuleDateBox { + width: 70%; +} + +.recurrenceDayButton { + width: 33px; + height: 33px; + border: 1px solid var(--bs-gray); + cursor: pointer; + transition: background-color 0.3s; + display: inline-flex; + justify-content: center; + align-items: center; + margin-right: 0.5rem; + border-radius: 50%; + position: relative; + outline: none; +} + +.recurrenceDayButton:focus-visible { + outline: 2px solid var(--bs-primary); + outline-offset: 2px; +} + +.recurrenceDayButton:hover { + background-color: var(--bs-gray); +} + +.recurrenceDayButton.selected { + background-color: var(--bs-primary); + border-color: var(--bs-primary); + color: var(--bs-white); +} + +.recurrenceDayButton span { + color: var(--bs-gray); + padding: 0.25rem; + text-align: center; +} + +.recurrenceDayButton:hover span { + color: var(--bs-white); +} + +.recurrenceDayButton.selected span { + color: var(--bs-white); +} + +.recurrenceRuleSubmitBtn { + display: block; + margin-left: auto; + padding: 7px 15px; + transition: all 0.2s ease; + border-radius: 4px; +} + +.recurrenceRuleSubmitBtn:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.recurrenceRuleSubmitBtn:focus-visible { + outline: 2px solid var(--bs-primary); + outline-offset: 2px; +} + +.attendance-modal .borderRightGreen { + border-right: 1px solid green; +} +.attendance-modal .positionedTopRight { + top: 10px; + right: 15px; + z-index: 1; +} +.attendance-modal .topRightCorner { + position: absolute; + right: 0; + top: 0; + border-bottom-left-radius: 8px; +} +.attendance-modal .bottomRightCorner { + position: absolute; + right: 0; + bottom: 0; + border-top-left-radius: 12px; +} +.attendance-modal .topLeftCorner { + position: absolute; + left: 0; + top: 0; + border-bottom-right-radius: 8px; +} +.attendance-modal .largeBoldText { + font-size: 80px; + font-weight: 400; +} +.attendance-modal .paddingBottom30 { + padding-bottom: 30px; +} +.attendance-modal .paddingBottom2Rem { + padding-bottom: 2rem; +} + +/* PostCard.tsx */ + +.cardStyles { + width: 100%; + max-width: 20rem; + background-color: var(--bs-white); + padding: 0; + border: none !important; + outline: none !important; +} + +.cardHeaderPostCard { + display: flex; + width: 100%; + padding-inline: 0; + padding-block: 0; + flex-direction: row; + gap: 0.5rem; + align-items: center; + background-color: var(--bs-white); + border-bottom: 1px solid var(--input-shadow-color); +} + +.creator { + display: flex; + width: 100%; + padding-inline: 1rem; + padding-block: 0; + flex-direction: row; + gap: 0.5rem; + align-items: center; +} +.creator p { + margin-bottom: 0; + font-weight: 500; +} +.creator svg { + width: 1.5rem; + height: 1.5rem; +} + +.customToggle { + padding: 0; + background: none; + border: none; + margin-right: 1rem; +} +.customToggle svg { + color: var(--bs-black); +} + +.customToggle::after { + content: none; +} +.customToggle:hover, +.customToggle:focus, +.customToggle:active { + background: none; + border: none; +} +.cardBodyPostCard div { + padding: 0.5rem; +} + +.imageContainerPostCard { + max-width: 100%; +} + +.cardTitlePostCard { + --max-lines: 1; + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: var(--max-lines); + + font-size: 1.3rem !important; + font-weight: 600; +} + +.date { + font-weight: 600; +} + +.cardText { + --max-lines: 2; + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: var(--max-lines); + padding-top: 0; + font-weight: 300; + margin-top: 0.7rem !important; + text-align: left; +} +.viewBtn { + display: flex; + justify-content: flex-end; + margin: 0.5rem; +} +.viewBtn Button { + padding-inline: 1rem; +} +.cardActions { + display: flex; + flex-direction: row; + align-items: center; + gap: 1px; + justify-content: flex-end; +} + +.cardActionBtn { + background-color: rgba(0, 0, 0, 0); + padding: 0; + border: none; + color: black; + transition: all 0.2s ease-in-out; + border-radius: 4px; +} + +.cardActionBtn:hover, +.cardActionBtn:focus-visible { + background-color: ghostwhite; + border: none; + color: black !important; + outline: 2px solid var(--subtle-blue-grey); + outline-offset: 2px; +} + +.cardActionBtn:active { + transform: scale(0.95); + background-color: var(--grey-bg-color); +} + +.creatorNameModal { + display: flex; + flex-direction: row; + gap: 5px; + align-items: center; + margin-bottom: 10px; +} + +.modalActions { + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + margin: 5px 0px; +} + +.textModal { + margin-top: 10px; +} + +.colorPrimary { + background: var(--subtle-blue-grey); + color: var(--bs-white); + cursor: pointer; +} + +.commentContainer { + overflow: auto; + max-height: 18rem; + padding-bottom: 1rem; +} + +.modalFooter { + background-color: var(--bs-white); + width: 100%; + padding-block: 0.5rem; + display: flex; + flex-direction: column; + border-top: 1px solid var(--input-shadow-color); +} + +.inputArea { + border: none; + outline: none; + background-color: var(--input-area-color); +} + +.postImage { + width: 100%; + aspect-ratio: 16/9; + object-fit: cover; +} + +/* Events.tsx */ + +.borderNone { + border: none; +} + +.colorWhite { + color: var(--bs-white); +} + +.backgroundWhite { + background-color: var(--bs-white); +} + +.maxWidth { + max-width: 800px; +} + +.mainContainer { + margin-top: 2rem; + width: 100%; + max-width: 800px; + flex-grow: 3; + max-height: 90vh; + overflow: auto; + padding: 0 1rem; +} + +.selectType { + border-radius: 10px; +} +.dropdown__item { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.gap { + gap: var(--spacing-lg, 1.25rem); +} + +.paddingY { + padding: var(--spacing-xl, 1.875rem) 0; +} + +.eventActionsContainer { + display: flex; + flex-direction: row; + gap: 15px; + flex-wrap: wrap; +} +.datePicker { + border-radius: 10px; + height: 2.5rem; + text-align: center; + background-color: var(--date-picker-background); + border: none; + width: 100%; +} + +.modalBodyEvents { + display: flex; + flex-direction: column; + gap: 10px; +} + +.switchContainer { + display: flex; + align-items: center; +} + +.switches { + display: flex; + flex-direction: row; + gap: 20px; + flex-wrap: wrap; + margin-top: 20px; +} +.titlemodalEvents { + color: var(--grey-bg-color-dark); + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid var(--subtle-blue-grey); + width: 65%; +} + +.datedivEvents { + display: flex; + flex-direction: row; + margin-bottom: 15px; +} + +.dateboxEvents { + width: 90%; + border-radius: 7px; + border-color: var(--grey-border-box-color); + outline: none; + box-shadow: none; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 5px; + padding-left: 5px; + margin-right: 5px; + margin-left: 5px; +} + +.checkboxdivEvents > label { + margin-right: 50px; +} +.checkboxdivEvents > label > input { + margin-left: 10px; +} + +.checkboxdivEvents { + display: flex; +} +.checkboxdivEvents > div { + width: 50%; +} + +.dispflexEvents { + display: flex; + align-items: center; +} +.dispflexEvents > input { + border: none; + box-shadow: none; + margin-top: 5px; +} + +.blueregbtnEvents { + margin-top: var(--spacing-lg, 1.25rem); + border: 1px solid var(--grey-border-box-color); + box-shadow: 0 2px 2px var(--grey-border-box-color); + padding: var(--spacing-md, 0.625rem); + border-radius: 5px; + background-color: var(--subtle-blue-grey); + width: 100%; + font-size: 16px; + color: var(--bs-white); + outline: none; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease-in-out; + width: 100%; + &:hover { + transform: translateY(-1px); + box-shadow: 0 4px 6px var(--grey-border-box-color); + } +} + +/* CreateDirectChat.tsx */ + +.modalContent { + width: 530px; +} + +.inputContainer { + position: relative; + margin-bottom: 16px; +} + +.inputFieldModal { + padding-right: 40px; + width: 100%; + border-radius: 4px; + border: 1px solid #ced4da; +} + +.submitBtn { + position: absolute; + z-index: 10; + bottom: 10px; + right: 0px; + display: flex; + justify-content: center; + align-items: center; +} +.tableContainer { + height: 400px; + overflow-y: scroll; + overflow-x: hidden; +} diff --git a/src/utils/chartToPdf.spec.ts b/src/utils/chartToPdf.spec.ts new file mode 100644 index 0000000000..e1ab67fdb0 --- /dev/null +++ b/src/utils/chartToPdf.spec.ts @@ -0,0 +1,153 @@ +import { expect, describe, test, beforeEach, afterEach, vi } from 'vitest'; +import type { Mock } from 'vitest'; +import { + exportToCSV, + exportTrendsToCSV, + exportDemographicsToCSV, +} from './chartToPdf'; + +describe('CSV Export Functions', () => { + // Define more specific types for our mocks + let mockCreateElement: Mock; + let mockAppendChild: Mock; + let mockRemoveChild: Mock; + let mockClick: Mock; + let mockSetAttribute: Mock; + let mockLink: HTMLAnchorElement; + + beforeEach(() => { + // Mock URL methods with specific types + (global.URL.createObjectURL as unknown) = vi.fn(() => 'mock-url'); + (global.URL.revokeObjectURL as unknown) = vi.fn(); + + // Mock DOM methods + mockSetAttribute = vi.fn(); + mockClick = vi.fn(); + mockLink = { + setAttribute: mockSetAttribute, + click: mockClick, + parentNode: document.body, + } as unknown as HTMLAnchorElement; + + // Mock createElement with proper type assertion + mockCreateElement = vi.fn().mockReturnValue(mockLink); + document.createElement = + mockCreateElement as unknown as typeof document.createElement; + + // Mock appendChild and removeChild with proper type assertions + mockAppendChild = vi.fn().mockReturnValue(mockLink); + mockRemoveChild = vi.fn().mockReturnValue(mockLink); + + document.body.appendChild = + mockAppendChild as unknown as typeof document.body.appendChild; + document.body.removeChild = + mockRemoveChild as unknown as typeof document.body.removeChild; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + test('exports data to CSV with proper formatting', () => { + const data: string[][] = [ + ['Header1', 'Header2'], + ['Value1', 'Value2'], + ['Value with, comma', 'Value with "quotes"'], + ]; + + exportToCSV(data, 'test.csv'); + + expect(mockCreateElement).toHaveBeenCalledWith('a'); + expect(mockSetAttribute).toHaveBeenCalledWith('href', 'mock-url'); + expect(mockSetAttribute).toHaveBeenCalledWith('download', 'test.csv'); + expect(mockAppendChild).toHaveBeenCalled(); + expect(mockClick).toHaveBeenCalled(); + expect(mockRemoveChild).toHaveBeenCalled(); + expect(URL.revokeObjectURL).toHaveBeenCalledWith('mock-url'); + }); + + test('throws error if data is empty', () => { + expect(() => exportToCSV([], 'test.csv')).toThrow('Data cannot be empty'); + }); + + test('throws error if filename is empty', () => { + expect(() => exportToCSV([['data']], '')).toThrow('Filename is required'); + }); + + test('adds .csv extension if missing', () => { + const data = [['test']]; + exportToCSV(data, 'filename'); + expect(mockSetAttribute).toHaveBeenCalledWith('download', 'filename.csv'); + }); + + describe('exportTrendsToCSV', () => { + test('exports attendance trends data correctly', () => { + const eventLabels: string[] = ['Event1', 'Event2']; + const attendeeCounts: number[] = [10, 20]; + const maleCounts: number[] = [5, 10]; + const femaleCounts: number[] = [4, 8]; + const otherCounts: number[] = [1, 2]; + + exportTrendsToCSV( + eventLabels, + attendeeCounts, + maleCounts, + femaleCounts, + otherCounts, + ); + + expect(mockCreateElement).toHaveBeenCalledWith('a'); + expect(mockSetAttribute).toHaveBeenCalledWith( + 'download', + 'attendance_trends.csv', + ); + expect(mockClick).toHaveBeenCalled(); + }); + }); + + describe('exportDemographicsToCSV', () => { + test('exports demographics data correctly', () => { + const selectedCategory = 'Age Groups'; + const categoryLabels: string[] = ['0-18', '19-30', '31+']; + const categoryData: number[] = [10, 20, 15]; + + exportDemographicsToCSV(selectedCategory, categoryLabels, categoryData); + + expect(mockCreateElement).toHaveBeenCalledWith('a'); + expect(mockClick).toHaveBeenCalled(); + expect(mockSetAttribute).toHaveBeenCalledWith('href', 'mock-url'); + }); + + test('throws error if selected category is empty', () => { + expect(() => exportDemographicsToCSV('', ['label'], [1])).toThrow( + 'Selected category is required', + ); + }); + + test('throws error if labels and data arrays have different lengths', () => { + expect(() => + exportDemographicsToCSV('Category', ['label1', 'label2'], [1]), + ).toThrow('Labels and data arrays must have the same length'); + }); + + test('creates safe filename with timestamp', () => { + vi.useFakeTimers(); + const mockDate = new Date('2023-01-01T00:00:00.000Z'); + vi.setSystemTime(mockDate); + + const selectedCategory = 'Age & Demographics!'; + const categoryLabels = ['Group1']; + const categoryData = [10]; + + exportDemographicsToCSV(selectedCategory, categoryLabels, categoryData); + + const expectedFilename = + 'age___demographics__demographics_2023-01-01T00-00-00.000Z.csv'; + const downloadCalls = mockSetAttribute.mock.calls.filter( + (call) => call[0] === 'download', + ); + expect(downloadCalls[0][1]).toBe(expectedFilename); + vi.useRealTimers(); + }); + }); +}); diff --git a/src/utils/chartToPdf.test.ts b/src/utils/chartToPdf.test.ts deleted file mode 100644 index b3094fff02..0000000000 --- a/src/utils/chartToPdf.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { - exportToCSV, - exportTrendsToCSV, - exportDemographicsToCSV, -} from './chartToPdf'; - -describe('CSV Export Functions', () => { - let mockCreateElement: jest.SpyInstance; - let mockClick: jest.SpyInstance; - let mockSetAttribute: jest.SpyInstance; - - beforeEach(() => { - // Mock URL methods - global.URL.createObjectURL = jest.fn(() => 'mock-url'); - global.URL.revokeObjectURL = jest.fn(); - - // Mock DOM methods - mockSetAttribute = jest.fn(); - mockClick = jest.fn(); - const mockLink = { - setAttribute: mockSetAttribute, - click: mockClick, - } as unknown as HTMLAnchorElement; - - mockCreateElement = jest - .spyOn(document, 'createElement') - .mockReturnValue(mockLink as HTMLAnchorElement); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('CSV Export Functions', () => { - let mockCreateElement: jest.SpyInstance; - let mockAppendChild: jest.SpyInstance; - let mockRemoveChild: jest.SpyInstance; - let mockClick: jest.SpyInstance; - let mockSetAttribute: jest.SpyInstance; - - beforeEach(() => { - // Mock URL methods - global.URL.createObjectURL = jest.fn(() => 'mock-url'); - global.URL.revokeObjectURL = jest.fn(); - - // Mock DOM methods - mockSetAttribute = jest.fn(); - mockClick = jest.fn(); - const mockLink = { - setAttribute: mockSetAttribute, - click: mockClick, - parentNode: document.body, // Add this to trigger removeChild - } as unknown as HTMLAnchorElement; - - mockCreateElement = jest - .spyOn(document, 'createElement') - .mockReturnValue(mockLink as HTMLAnchorElement); - mockAppendChild = jest - .spyOn(document.body, 'appendChild') - .mockImplementation(() => mockLink as HTMLAnchorElement); - mockRemoveChild = jest - .spyOn(document.body, 'removeChild') - .mockImplementation(() => mockLink as HTMLAnchorElement); - }); - - test('exports data to CSV with proper formatting', () => { - const data = [ - ['Header1', 'Header2'], - ['Value1', 'Value2'], - ['Value with, comma', 'Value with "quotes"'], - ]; - - exportToCSV(data, 'test.csv'); - - expect(mockCreateElement).toHaveBeenCalledWith('a'); - expect(mockSetAttribute).toHaveBeenCalledWith('href', 'mock-url'); - expect(mockSetAttribute).toHaveBeenCalledWith('download', 'test.csv'); - expect(mockAppendChild).toHaveBeenCalled(); - expect(mockClick).toHaveBeenCalled(); - expect(mockRemoveChild).toHaveBeenCalled(); - expect(URL.revokeObjectURL).toHaveBeenCalledWith('mock-url'); - }); - test('throws error if data is empty', () => { - expect(() => exportToCSV([], 'test.csv')).toThrow('Data cannot be empty'); - }); - - test('throws error if filename is empty', () => { - expect(() => exportToCSV([['data']], '')).toThrow('Filename is required'); - }); - - test('adds .csv extension if missing', () => { - const data = [['test']]; - exportToCSV(data, 'filename'); - expect(mockSetAttribute).toHaveBeenCalledWith('download', 'filename.csv'); - }); - }); - - describe('exportTrendsToCSV', () => { - test('exports attendance trends data correctly', () => { - const eventLabels = ['Event1', 'Event2']; - const attendeeCounts = [10, 20]; - const maleCounts = [5, 10]; - const femaleCounts = [4, 8]; - const otherCounts = [1, 2]; - - exportTrendsToCSV( - eventLabels, - attendeeCounts, - maleCounts, - femaleCounts, - otherCounts, - ); - - expect(mockCreateElement).toHaveBeenCalledWith('a'); - expect(mockSetAttribute).toHaveBeenCalledWith( - 'download', - 'attendance_trends.csv', - ); - expect(mockClick).toHaveBeenCalled(); - }); - }); - - describe('exportDemographicsToCSV', () => { - test('exports demographics data correctly', () => { - const selectedCategory = 'Age Groups'; - const categoryLabels = ['0-18', '19-30', '31+']; - const categoryData = [10, 20, 15]; - - exportDemographicsToCSV(selectedCategory, categoryLabels, categoryData); - - expect(mockCreateElement).toHaveBeenCalledWith('a'); - expect(mockClick).toHaveBeenCalled(); - expect(mockSetAttribute).toHaveBeenCalledWith('href', 'mock-url'); - }); - - test('throws error if selected category is empty', () => { - expect(() => exportDemographicsToCSV('', ['label'], [1])).toThrow( - 'Selected category is required', - ); - }); - - test('throws error if labels and data arrays have different lengths', () => { - expect(() => - exportDemographicsToCSV('Category', ['label1', 'label2'], [1]), - ).toThrow('Labels and data arrays must have the same length'); - }); - - test('creates safe filename with timestamp', () => { - jest.useFakeTimers(); - const mockDate = new Date('2023-01-01T00:00:00.000Z'); - jest.setSystemTime(mockDate); - - const selectedCategory = 'Age & Demographics!'; - const categoryLabels = ['Group1']; - const categoryData = [10]; - - exportDemographicsToCSV(selectedCategory, categoryLabels, categoryData); - - const expectedFilename = - 'age___demographics__demographics_2023-01-01T00-00-00.000Z.csv'; - const downloadCalls = mockSetAttribute.mock.calls.filter( - (call) => call[0] === 'download', - ); - expect(downloadCalls[0][1]).toBe(expectedFilename); - jest.useRealTimers(); - }); - }); -}); diff --git a/src/utils/convertToBase64.test.ts b/src/utils/convertToBase64.spec.ts similarity index 90% rename from src/utils/convertToBase64.test.ts rename to src/utils/convertToBase64.spec.ts index 51198ccc29..85b97ac31a 100644 --- a/src/utils/convertToBase64.test.ts +++ b/src/utils/convertToBase64.spec.ts @@ -1,3 +1,4 @@ +import { describe, it, expect, vi } from 'vitest'; import convertToBase64 from './convertToBase64'; describe('convertToBase64', () => { @@ -16,7 +17,7 @@ describe('convertToBase64', () => { it('should handle errors thrown by FileReader', async () => { // Arrange const file = new File(['hello'], 'hello.txt', { type: 'text/plain' }); - const mockFileReader = jest + const mockFileReader = vi .spyOn(global, 'FileReader') .mockImplementationOnce(() => { throw new Error('Test error'); @@ -29,5 +30,6 @@ describe('convertToBase64', () => { expect(mockFileReader).toHaveBeenCalledTimes(1); expect(mockFileReader).toHaveBeenCalledWith(); expect(result).toBe(''); + mockFileReader.mockRestore(); }); }); diff --git a/src/utils/errorHandler.test.tsx b/src/utils/errorHandler.spec.tsx similarity index 96% rename from src/utils/errorHandler.test.tsx rename to src/utils/errorHandler.spec.tsx index f229e8d5fa..96c35e2a7f 100644 --- a/src/utils/errorHandler.test.tsx +++ b/src/utils/errorHandler.spec.tsx @@ -2,10 +2,11 @@ type TFunction = (key: string, options?: Record) => string; import { errorHandler } from './errorHandler'; import { toast } from 'react-toastify'; +import { describe, it, expect, vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - error: jest.fn(), + error: vi.fn(), }, })); @@ -22,7 +23,7 @@ describe('Test if errorHandler is working properly', () => { }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should call toast.error with the correct message if error message is "Failed to fetch"', async () => { diff --git a/src/utils/getRefreshToken.spec.ts b/src/utils/getRefreshToken.spec.ts new file mode 100644 index 0000000000..54ed1b57e8 --- /dev/null +++ b/src/utils/getRefreshToken.spec.ts @@ -0,0 +1,87 @@ +// SKIP_LOCALSTORAGE_CHECK +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { refreshToken } from './getRefreshToken'; + +const mockApolloClient = { + mutate: vi.fn(() => + Promise.resolve({ + data: { + refreshToken: { + accessToken: 'newAccessToken', + refreshToken: 'newRefreshToken', + }, + }, + }), + ), +}; + +vi.mock('@apollo/client', async () => { + const actual = await vi.importActual('@apollo/client'); + return { + ...actual, + ApolloClient: vi.fn(() => mockApolloClient), + }; +}); + +describe('refreshToken', () => { + const { location } = window; + + interface TestInterfacePartialWindow { + location?: Partial; + } + + delete (window as TestInterfacePartialWindow).location; + global.window.location = { ...location, reload: vi.fn() }; + + // Create storage mock + const localStorageMock = { + getItem: vi.fn(), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + length: 0, + key: vi.fn(), + }; + + beforeEach(() => { + vi.clearAllMocks(); + Object.defineProperty(window, 'localStorage', { + value: localStorageMock, + writable: true, + }); + }); + + it('returns true when the token is refreshed successfully', async () => { + const result = await refreshToken(); + + expect(localStorage.setItem).toHaveBeenCalledWith( + 'Talawa-admin_token', + JSON.stringify('newAccessToken'), + ); + expect(localStorage.setItem).toHaveBeenCalledWith( + 'Talawa-admin_refreshToken', + JSON.stringify('newRefreshToken'), + ); + expect(result).toBe(true); + expect(window.location.reload).toHaveBeenCalled(); + }); + + it('returns false and logs error when token refresh fails', async () => { + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + const errorMock = new Error('Failed to refresh token'); + mockApolloClient.mutate.mockRejectedValueOnce(errorMock); + + const result = await refreshToken(); + + expect(result).toBe(false); + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'Failed to refresh token', + errorMock, + ); + + consoleErrorSpy.mockRestore(); + }); +}); diff --git a/src/utils/getRefreshToken.test.ts b/src/utils/getRefreshToken.test.ts deleted file mode 100644 index 58de898a66..0000000000 --- a/src/utils/getRefreshToken.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -// SKIP_LOCALSTORAGE_CHECK -import { refreshToken } from './getRefreshToken'; - -jest.mock('@apollo/client', () => { - const originalModule = jest.requireActual('@apollo/client'); - - return { - __esModule: true, - ...originalModule, - ApolloClient: jest.fn(() => ({ - mutate: jest.fn(() => - Promise.resolve({ - data: { - refreshToken: { - accessToken: 'newAccessToken', - refreshToken: 'newRefreshToken', - }, - }, - }), - ), - })), - }; -}); - -describe('refreshToken', () => { - // Mock window.location.reload() - const { location } = window; - delete (global.window as any).location; - global.window.location = { ...location, reload: jest.fn() }; - - // Mock localStorage.setItem() and localStorage.clear() - - Storage.prototype.setItem = jest.fn(); - Storage.prototype.clear = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('returns true when the token is refreshed successfully', async () => { - const result = await refreshToken(); - - expect(localStorage.setItem).toHaveBeenCalledWith( - 'Talawa-admin_token', - JSON.stringify('newAccessToken'), - ); - expect(localStorage.setItem).toHaveBeenCalledWith( - 'Talawa-admin_refreshToken', - JSON.stringify('newRefreshToken'), - ); - expect(result).toBe(true); - expect(window.location.reload).toHaveBeenCalled(); - }); -}); diff --git a/src/utils/getRefreshToken.ts b/src/utils/getRefreshToken.ts index 5d6f8aa2ce..90b1b55ef1 100644 --- a/src/utils/getRefreshToken.ts +++ b/src/utils/getRefreshToken.ts @@ -14,7 +14,7 @@ export async function refreshToken(): Promise { const { getItem, setItem } = useLocalStorage(); const refreshToken = getItem('refreshToken'); - /* istanbul ignore next */ + try { const { data } = await client.mutate({ mutation: REFRESH_TOKEN_MUTATION, diff --git a/src/utils/timezoneUtils/dateTimeMiddleware.test.ts b/src/utils/timezoneUtils/dateTimeMiddleware.spec.ts similarity index 92% rename from src/utils/timezoneUtils/dateTimeMiddleware.test.ts rename to src/utils/timezoneUtils/dateTimeMiddleware.spec.ts index b14702b546..ead19444c3 100644 --- a/src/utils/timezoneUtils/dateTimeMiddleware.test.ts +++ b/src/utils/timezoneUtils/dateTimeMiddleware.spec.ts @@ -3,6 +3,7 @@ import type { Operation, FetchResult } from '@apollo/client/core'; import { Observable } from '@apollo/client/core'; import { gql } from '@apollo/client'; import type { DocumentNode } from 'graphql'; +import { describe, it, expect, vi } from 'vitest'; const DUMMY_QUERY: DocumentNode = gql` query GetDummyData { @@ -19,12 +20,12 @@ describe('Date Time Middleware Tests', () => { query: DUMMY_QUERY, operationName: 'GetDummyData', variables: { startDate: '2023-09-01', startTime: '12:00:00' }, - getContext: jest.fn(() => ({})), - setContext: jest.fn(), + getContext: vi.fn(() => ({})), + setContext: vi.fn(), extensions: {}, }; - const forward = jest.fn( + const forward = vi.fn( (op) => new Observable((observer) => { expect(op.variables['startDate']).toBe('2023-09-01'); @@ -55,12 +56,12 @@ describe('Date Time Middleware Tests', () => { query: DUMMY_QUERY, operationName: 'GetDummyData', variables: {}, - getContext: jest.fn(() => ({})), - setContext: jest.fn(), + getContext: vi.fn(() => ({})), + setContext: vi.fn(), extensions: {}, }; - const forward = jest.fn( + const forward = vi.fn( () => new Observable((observer) => { observer.next(testResponse); @@ -98,12 +99,12 @@ describe('Date Time Middleware Tests', () => { query: DUMMY_QUERY, operationName: 'GetDummyData', variables: { startDate: 'not-a-date', startTime: '25:99:99' }, - getContext: jest.fn(() => ({})), - setContext: jest.fn(), + getContext: vi.fn(() => ({})), + setContext: vi.fn(), extensions: {}, }; - const forward = jest.fn( + const forward = vi.fn( (op) => new Observable((observer) => { expect(op.variables['startDate']).toBe('not-a-date'); @@ -130,12 +131,12 @@ describe('Date Time Middleware Tests', () => { query: DUMMY_QUERY, operationName: 'GetDummyData', variables: {}, - getContext: jest.fn(() => ({})), - setContext: jest.fn(), + getContext: vi.fn(() => ({})), + setContext: vi.fn(), extensions: {}, }; - const forward = jest.fn( + const forward = vi.fn( () => new Observable((observer) => { observer.next(testResponse); @@ -188,12 +189,12 @@ describe('Date Time Middleware Tests', () => { }, }, }, - getContext: jest.fn(() => ({})), - setContext: jest.fn(), + getContext: vi.fn(() => ({})), + setContext: vi.fn(), extensions: {}, }; - const forward = jest.fn( + const forward = vi.fn( (op) => new Observable((observer) => { expect(op.variables.event.startDate).toBe('2023-10-01'); diff --git a/src/utils/timezoneUtils/dateTimeMiddleware.ts b/src/utils/timezoneUtils/dateTimeMiddleware.ts index 70fc20a026..f3be59bacb 100644 --- a/src/utils/timezoneUtils/dateTimeMiddleware.ts +++ b/src/utils/timezoneUtils/dateTimeMiddleware.ts @@ -14,6 +14,13 @@ const combineDateTime = (date: string, time: string): string => { const splitDateTime = (dateTimeStr: string): { date: string; time: string } => { const dateTime = dayjs.utc(dateTimeStr); + if (!dateTime.isValid()) { + const [date, time] = dateTimeStr.split('T'); + return { + date: date, + time: time, + }; + } return { date: dateTime.format('YYYY-MM-DD'), time: dateTime.format('HH:mm:ss.SSS[Z]'), @@ -21,18 +28,17 @@ const splitDateTime = (dateTimeStr: string): { date: string; time: string } => { }; const convertUTCToLocal = (dateStr: string): string => { - if (dayjs(dateStr).isValid()) { - return dayjs.utc(dateStr).local().format('YYYY-MM-DDTHH:mm:ss.SSS'); + if (!dayjs(dateStr).isValid()) { + return dateStr; } - return dateStr; + return dayjs.utc(dateStr).local().format('YYYY-MM-DDTHH:mm:ss.SSS'); }; const convertLocalToUTC = (dateStr: string): string => { - if (dayjs(dateStr).isValid()) { - const result = dayjs(dateStr).utc().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'); - return result; + if (!dayjs(dateStr).isValid()) { + return dateStr; // Leave the invalid value unchanged } - return dateStr; + return dayjs(dateStr).utc().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'); }; const traverseAndConvertDates = ( diff --git a/src/utils/useLocalstorage.test.ts b/src/utils/useLocalstorage.spec.ts similarity index 91% rename from src/utils/useLocalstorage.test.ts rename to src/utils/useLocalstorage.spec.ts index 7483da4da2..b84cbd86cf 100644 --- a/src/utils/useLocalstorage.test.ts +++ b/src/utils/useLocalstorage.spec.ts @@ -1,3 +1,5 @@ +// SKIP_LOCALSTORAGE_CHECK + import { getStorageKey, getItem, @@ -5,6 +7,7 @@ import { removeItem, useLocalStorage, } from './useLocalstorage'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; describe('Storage Helper Functions', () => { beforeEach(() => { @@ -95,7 +98,7 @@ describe('Storage Helper Functions', () => { const storageHelper = useLocalStorage(customPrefix); const key = 'testKey'; - const spyGetStorageKey = jest.spyOn(storageHelper, 'getStorageKey'); + const spyGetStorageKey = vi.spyOn(storageHelper, 'getStorageKey'); storageHelper.getStorageKey(key); expect(spyGetStorageKey).toHaveBeenCalledWith(key); @@ -106,7 +109,7 @@ describe('Storage Helper Functions', () => { const storageHelper = useLocalStorage(customPrefix); const key = 'testKey'; - const spyGetItem = jest.spyOn(storageHelper, 'getItem'); + const spyGetItem = vi.spyOn(storageHelper, 'getItem'); storageHelper.getItem(key); expect(spyGetItem).toHaveBeenCalledWith(key); @@ -118,7 +121,7 @@ describe('Storage Helper Functions', () => { const key = 'testKey'; const value = 'data'; - const spySetItem = jest.spyOn(storageHelper, 'setItem'); + const spySetItem = vi.spyOn(storageHelper, 'setItem'); storageHelper.setItem(key, value); expect(spySetItem).toHaveBeenCalledWith(key, value); @@ -129,7 +132,7 @@ describe('Storage Helper Functions', () => { const storageHelper = useLocalStorage(customPrefix); const key = 'testKey'; - const spyRemoveItem = jest.spyOn(storageHelper, 'removeItem'); + const spyRemoveItem = vi.spyOn(storageHelper, 'removeItem'); storageHelper.removeItem(key); expect(spyRemoveItem).toHaveBeenCalledWith(key); diff --git a/src/utils/useSession.spec.tsx b/src/utils/useSession.spec.tsx new file mode 100644 index 0000000000..9b50039ba2 --- /dev/null +++ b/src/utils/useSession.spec.tsx @@ -0,0 +1,688 @@ +import type { ReactNode } from 'react'; +import React from 'react'; +import { renderHook } from '@testing-library/react'; +import { MockedProvider } from '@apollo/client/testing'; +import { toast } from 'react-toastify'; +import { describe, beforeEach, afterEach, test, expect, vi } from 'vitest'; +import useSession from './useSession'; +import { GET_COMMUNITY_SESSION_TIMEOUT_DATA } from 'GraphQl/Queries/Queries'; +import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; +import { errorHandler } from 'utils/errorHandler'; +import { BrowserRouter } from 'react-router-dom'; + +vi.mock('react-toastify', () => ({ + toast: { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock('utils/errorHandler', () => ({ + errorHandler: vi.fn(), +})); + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +const MOCKS = [ + { + request: { + query: GET_COMMUNITY_SESSION_TIMEOUT_DATA, + }, + result: { + data: { + getCommunityData: { + timeout: 30, + }, + }, + }, + delay: 100, + }, + { + request: { + query: REVOKE_REFRESH_TOKEN, + }, + result: { + data: { + revokeRefreshTokenForUser: true, + }, + }, + }, +]; +describe('useSession Hook', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.spyOn(window, 'addEventListener').mockImplementation(vi.fn()); + vi.spyOn(window, 'removeEventListener').mockImplementation(vi.fn()); + Object.defineProperty(global, 'localStorage', { + value: { + clear: vi.fn(), + }, + writable: true, + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + test('should handle visibility change to visible', async () => { + vi.useFakeTimers(); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + Object.defineProperty(document, 'visibilityState', { + value: 'visible', + writable: true, + }); + + result.current.startSession(); + + document.dispatchEvent(new Event('visibilitychange')); + + vi.advanceTimersByTime(15 * 60 * 1000); + + await vi.waitFor(() => { + expect(window.addEventListener).toHaveBeenCalledWith( + 'mousemove', + expect.any(Function), + ); + expect(window.addEventListener).toHaveBeenCalledWith( + 'keydown', + expect.any(Function), + ); + expect(toast.warning).toHaveBeenCalledWith('sessionWarning'); + }); + + vi.useRealTimers(); + }); + + test('should handle visibility change to hidden and ensure no warning appears in 15 minutes', async () => { + vi.useFakeTimers(); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + Object.defineProperty(document, 'visibilityState', { + value: 'hidden', + writable: true, + }); + + result.current.startSession(); + + document.dispatchEvent(new Event('visibilitychange')); + + vi.advanceTimersByTime(15 * 60 * 1000); + + await vi.waitFor(() => { + expect(window.removeEventListener).toHaveBeenCalledWith( + 'mousemove', + expect.any(Function), + ); + expect(window.removeEventListener).toHaveBeenCalledWith( + 'keydown', + expect.any(Function), + ); + expect(toast.warning).not.toHaveBeenCalled(); + }); + + vi.useRealTimers(); + }); + + test('should register event listeners on startSession', async () => { + const addEventListenerSpy = vi.fn(); + const windowAddEventListenerSpy = vi + .spyOn(window, 'addEventListener') + .mockImplementation(addEventListenerSpy); + const documentAddEventListenerSpy = vi + .spyOn(document, 'addEventListener') + .mockImplementation(addEventListenerSpy); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + + await vi.waitFor(() => { + const calls = addEventListenerSpy.mock.calls; + expect(calls.length).toBe(4); + + const eventTypes = calls.map((call) => call[0]); + expect(eventTypes).toContain('mousemove'); + expect(eventTypes).toContain('keydown'); + expect(eventTypes).toContain('visibilitychange'); + + calls.forEach((call) => { + expect(call[1]).toBeTypeOf('function'); + }); + }); + + windowAddEventListenerSpy.mockRestore(); + documentAddEventListenerSpy.mockRestore(); + }); + + test('should call handleLogout after session timeout', async () => { + vi.useFakeTimers(); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + + vi.advanceTimersByTime(31 * 60 * 1000); + + await vi.waitFor(() => { + expect(global.localStorage.clear).toHaveBeenCalled(); + expect(toast.warning).toHaveBeenCalledTimes(2); + expect(toast.warning).toHaveBeenNthCalledWith(1, 'sessionWarning'); + expect(toast.warning).toHaveBeenNthCalledWith(2, 'sessionLogout', { + autoClose: false, + }); + }); + }); + + test('should show a warning toast before session expiration', async () => { + vi.useFakeTimers(); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + + vi.advanceTimersByTime(15 * 60 * 1000); + + await vi.waitFor(() => + expect(toast.warning).toHaveBeenCalledWith('sessionWarning'), + ); + + vi.useRealTimers(); + }); + + test('should handle error when revoking token fails', async () => { + const consoleErrorMock = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + const errorMocks = [ + { + request: { + query: GET_COMMUNITY_SESSION_TIMEOUT_DATA, + }, + result: { + data: { + getCommunityData: { + timeout: 30, + }, + }, + }, + delay: 1000, + }, + { + request: { + query: REVOKE_REFRESH_TOKEN, + }, + error: new Error('Failed to revoke refresh token'), + }, + ]; + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + result.current.handleLogout(); + + await vi.waitFor(() => + expect(consoleErrorMock).toHaveBeenCalledWith( + 'Error revoking refresh token:', + expect.any(Error), + ), + ); + + consoleErrorMock.mockRestore(); + }); + + test('should set session timeout based on fetched data', async () => { + vi.spyOn(global, 'setTimeout'); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + + expect(global.setTimeout).toHaveBeenCalled(); + }); + + test('should call errorHandler on query error', async () => { + const errorMocks = [ + { + request: { + query: GET_COMMUNITY_SESSION_TIMEOUT_DATA, + }, + error: new Error('An error occurred'), + }, + ]; + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + + await vi.waitFor(() => expect(errorHandler).toHaveBeenCalled()); + }); + + test('should remove event listeners on endSession', async () => { + const removeEventListenerSpy = vi.fn(); + const windowRemoveEventListenerSpy = vi + .spyOn(window, 'removeEventListener') + .mockImplementation(removeEventListenerSpy); + const documentRemoveEventListenerSpy = vi + .spyOn(document, 'removeEventListener') + .mockImplementation(removeEventListenerSpy); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + result.current.endSession(); + + await vi.waitFor(() => { + const calls = removeEventListenerSpy.mock.calls; + expect(calls.length).toBe(6); + + const eventTypes = calls.map((call) => call[0]); + expect(eventTypes).toContain('mousemove'); + expect(eventTypes).toContain('keydown'); + expect(eventTypes).toContain('visibilitychange'); + + calls.forEach((call) => { + expect(call[1]).toBeTypeOf('function'); + }); + }); + + windowRemoveEventListenerSpy.mockRestore(); + documentRemoveEventListenerSpy.mockRestore(); + }); + + test('should call initialize timers when session is still active when the user returns to the tab', async () => { + vi.useFakeTimers(); + vi.spyOn(global, 'setTimeout').mockImplementation(vi.fn()); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + vi.advanceTimersByTime(1000); + + Object.defineProperty(document, 'visibilityState', { + value: 'visible', + writable: true, + }); + + result.current.startSession(); + vi.advanceTimersByTime(10 * 60 * 1000); + + Object.defineProperty(document, 'visibilityState', { + value: 'hidden', + writable: true, + }); + + document.dispatchEvent(new Event('visibilitychange')); + + vi.advanceTimersByTime(5 * 60 * 1000); + + Object.defineProperty(document, 'visibilityState', { + value: 'visible', + writable: true, + }); + + document.dispatchEvent(new Event('visibilitychange')); + + vi.advanceTimersByTime(1000); + + expect(global.setTimeout).toHaveBeenCalled(); + + vi.useRealTimers(); + }); + + test('should call handleLogout when session expires due to inactivity away from tab', async () => { + vi.useFakeTimers(); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + vi.advanceTimersByTime(1000); + + Object.defineProperty(document, 'visibilityState', { + value: 'visible', + writable: true, + }); + + result.current.startSession(); + vi.advanceTimersByTime(10 * 60 * 1000); + + Object.defineProperty(document, 'visibilityState', { + value: 'hidden', + writable: true, + }); + + document.dispatchEvent(new Event('visibilitychange')); + + vi.advanceTimersByTime(32 * 60 * 1000); + + Object.defineProperty(document, 'visibilityState', { + value: 'visible', + writable: true, + }); + + document.dispatchEvent(new Event('visibilitychange')); + + vi.advanceTimersByTime(250); + + await vi.waitFor(() => { + expect(global.localStorage.clear).toHaveBeenCalled(); + expect(toast.warning).toHaveBeenCalledWith('sessionLogout', { + autoClose: false, + }); + }); + + vi.useRealTimers(); + }); + + test('should handle logout and revoke token', async () => { + vi.useFakeTimers(); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + result.current.handleLogout(); + + await vi.waitFor(() => { + expect(global.localStorage.clear).toHaveBeenCalled(); + expect(toast.warning).toHaveBeenCalledWith('sessionLogout', { + autoClose: false, + }); + }); + + vi.useRealTimers(); + }); +}); +test('should extend session when called directly', async () => { + vi.useFakeTimers(); + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + + // Advance time to just before warning + vi.advanceTimersByTime(14 * 60 * 1000); + + // Extend session + result.current.extendSession(); + + // Advance time to where warning would have been + vi.advanceTimersByTime(1 * 60 * 1000); + + // Warning shouldn't have been called yet since we extended + expect(toast.warning).not.toHaveBeenCalled(); + + // Advance to new warning time + vi.advanceTimersByTime(14 * 60 * 1000); + + await vi.waitFor(() => + expect(toast.warning).toHaveBeenCalledWith('sessionWarning'), + ); + + vi.useRealTimers(); +}); + +test('should properly clean up on unmount', () => { + // Mock document.removeEventListener + const documentRemoveEventListener = vi.spyOn(document, 'removeEventListener'); + + const { result, unmount } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + unmount(); + + expect(window.removeEventListener).toHaveBeenCalledWith( + 'mousemove', + expect.any(Function), + ); + expect(window.removeEventListener).toHaveBeenCalledWith( + 'keydown', + expect.any(Function), + ); + expect(documentRemoveEventListener).toHaveBeenCalledWith( + 'visibilitychange', + expect.any(Function), + ); + + documentRemoveEventListener.mockRestore(); +}); +test('should handle missing community data', async () => { + vi.useFakeTimers(); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); + + const nullDataMocks = [ + { + request: { + query: GET_COMMUNITY_SESSION_TIMEOUT_DATA, + }, + result: { + data: { + getCommunityData: null, + }, + }, + }, + ]; + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + + // Wait for timers to be set + await vi.waitFor(() => { + expect(setTimeoutSpy).toHaveBeenCalled(); + }); + + // Get all setTimeout calls + const timeoutCalls = setTimeoutSpy.mock.calls; + + // Check for warning timeout (15 minutes = 900000ms) + const hasWarningTimeout = timeoutCalls.some( + (call: Parameters) => { + const [, ms] = call; + return typeof ms === 'number' && ms === (30 * 60 * 1000) / 2; + }, + ); + + // Check for session timeout (30 minutes = 1800000ms) + const hasSessionTimeout = timeoutCalls.some( + (call: Parameters) => { + const [, ms] = call; + return typeof ms === 'number' && ms === 30 * 60 * 1000; + }, + ); + + expect(hasWarningTimeout).toBe(true); + expect(hasSessionTimeout).toBe(true); + + setTimeoutSpy.mockRestore(); + vi.useRealTimers(); +}); + +test('should handle event listener errors gracefully', async () => { + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + const mockError = new Error('Event listener error'); + + // Mock addEventListener to throw an error + const addEventListenerSpy = vi + .spyOn(window, 'addEventListener') + .mockImplementationOnce(() => { + throw mockError; + }); + + try { + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + } catch { + // Error should be caught and logged + expect(consoleErrorSpy).toHaveBeenCalled(); + } + + consoleErrorSpy.mockRestore(); + addEventListenerSpy.mockRestore(); +}); + +test('should handle session timeout data updates', async () => { + vi.useFakeTimers(); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); + + const customMocks = [ + { + request: { + query: GET_COMMUNITY_SESSION_TIMEOUT_DATA, + }, + result: { + data: { + getCommunityData: { + timeout: 45, + }, + }, + }, + }, + ]; + + const { result } = renderHook(() => useSession(), { + wrapper: ({ children }: { children?: ReactNode }) => ( + + {children} + + ), + }); + + result.current.startSession(); + + // Wait for the query and timers + await vi.waitFor(() => { + expect(setTimeoutSpy).toHaveBeenCalled(); + }); + + const timeoutCalls = setTimeoutSpy.mock.calls; + const expectedWarningTime = (45 * 60 * 1000) / 2; + const expectedSessionTime = 45 * 60 * 1000; + + const hasWarningTimeout = timeoutCalls.some((call) => { + const duration = call[1] as number; + return ( + Math.abs(duration - expectedWarningTime) <= expectedWarningTime * 0.05 + ); // ±5% + }); + + const hasSessionTimeout = timeoutCalls.some((call) => { + const duration = call[1] as number; + return ( + Math.abs(duration - expectedSessionTime) <= expectedSessionTime * 0.05 + ); // ±5% + }); + + expect(hasWarningTimeout).toBe(false); + expect(hasSessionTimeout).toBe(false); + + setTimeoutSpy.mockRestore(); + vi.useRealTimers(); +}); diff --git a/src/utils/useSession.test.tsx b/src/utils/useSession.test.tsx deleted file mode 100644 index 32287ccbb0..0000000000 --- a/src/utils/useSession.test.tsx +++ /dev/null @@ -1,544 +0,0 @@ -import type { ReactNode } from 'react'; -import React from 'react'; -import { renderHook, act, waitFor } from '@testing-library/react'; -import { MockedProvider } from '@apollo/client/testing'; -import { toast } from 'react-toastify'; -import useSession from './useSession'; -import { GET_COMMUNITY_SESSION_TIMEOUT_DATA } from 'GraphQl/Queries/Queries'; -import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; -import { errorHandler } from 'utils/errorHandler'; -import { BrowserRouter } from 'react-router-dom'; - -jest.mock('react-toastify', () => ({ - toast: { - info: jest.fn(), - warning: jest.fn(), - error: jest.fn(), - }, -})); - -jest.mock('utils/errorHandler', () => ({ - errorHandler: jest.fn(), -})); - -jest.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key: string) => key, - }), -})); - -const MOCKS = [ - { - request: { - query: GET_COMMUNITY_SESSION_TIMEOUT_DATA, - }, - result: { - data: { - getCommunityData: { - timeout: 30, - }, - }, - }, - delay: 100, - }, - { - request: { - query: REVOKE_REFRESH_TOKEN, - }, - result: { - data: { - revokeRefreshTokenForUser: true, - }, - }, - }, -]; - -const wait = (ms: number): Promise => - new Promise((resolve) => setTimeout(resolve, ms)); - -describe('useSession Hook', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.spyOn(window, 'addEventListener').mockImplementation(jest.fn()); - jest.spyOn(window, 'removeEventListener').mockImplementation(jest.fn()); - Object.defineProperty(global, 'localStorage', { - value: { - clear: jest.fn(), - }, - writable: true, - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - jest.useRealTimers(); - jest.restoreAllMocks(); - }); - - test('should handle visibility change to visible', async () => { - jest.useFakeTimers(); - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - Object.defineProperty(document, 'visibilityState', { - value: 'visible', - writable: true, - }); - - act(() => { - result.current.startSession(); - }); - - // Simulate visibility change - act(() => { - document.dispatchEvent(new Event('visibilitychange')); - }); - - act(() => { - jest.advanceTimersByTime(15 * 60 * 1000); - }); - - await waitFor(() => { - expect(window.addEventListener).toHaveBeenCalledWith( - 'mousemove', - expect.any(Function), - ); - expect(window.addEventListener).toHaveBeenCalledWith( - 'keydown', - expect.any(Function), - ); - expect(toast.warning).toHaveBeenCalledWith('sessionWarning'); - }); - - jest.useRealTimers(); - }); - - test('should handle visibility change to hidden and ensure no warning appears in 15 minutes', async () => { - jest.useFakeTimers(); - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - Object.defineProperty(document, 'visibilityState', { - value: 'hidden', - writable: true, - }); - - act(() => { - result.current.startSession(); - }); - - act(() => { - document.dispatchEvent(new Event('visibilitychange')); - }); - - act(() => { - jest.advanceTimersByTime(15 * 60 * 1000); - }); - - await waitFor(() => { - expect(window.removeEventListener).toHaveBeenCalledWith( - 'mousemove', - expect.any(Function), - ); - expect(window.removeEventListener).toHaveBeenCalledWith( - 'keydown', - expect.any(Function), - ); - expect(toast.warning).not.toHaveBeenCalled(); - }); - - jest.useRealTimers(); - }); - - test('should register event listeners on startSession', async () => { - const addEventListenerMock = jest.fn(); - const originalWindowAddEventListener = window.addEventListener; - const originalDocumentAddEventListener = document.addEventListener; - - window.addEventListener = addEventListenerMock; - document.addEventListener = addEventListenerMock; - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - act(() => { - result.current.startSession(); - }); - - expect(addEventListenerMock).toHaveBeenCalledWith( - 'mousemove', - expect.any(Function), - ); - expect(addEventListenerMock).toHaveBeenCalledWith( - 'keydown', - expect.any(Function), - ); - expect(addEventListenerMock).toHaveBeenCalledWith( - 'visibilitychange', - expect.any(Function), - ); - - window.addEventListener = originalWindowAddEventListener; - document.addEventListener = originalDocumentAddEventListener; - }); - - test('should call handleLogout after session timeout', async () => { - jest.useFakeTimers(); - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - act(() => { - result.current.startSession(); - }); - - act(() => { - jest.advanceTimersByTime(31 * 60 * 1000); - }); - - await waitFor(() => { - expect(global.localStorage.clear).toHaveBeenCalled(); - expect(toast.warning).toHaveBeenCalledTimes(2); - expect(toast.warning).toHaveBeenNthCalledWith(1, 'sessionWarning'); - expect(toast.warning).toHaveBeenNthCalledWith(2, 'sessionLogout', { - autoClose: false, - }); - }); - }); - - test('should show a warning toast before session expiration', async () => { - jest.useFakeTimers(); - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - act(() => { - result.current.startSession(); - }); - - act(() => { - jest.advanceTimersByTime(15 * 60 * 1000); - }); - - await waitFor(() => - expect(toast.warning).toHaveBeenCalledWith('sessionWarning'), - ); - - jest.useRealTimers(); - }); - - test('should handle error when revoking token fails', async () => { - const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(); - - const errorMocks = [ - { - request: { - query: GET_COMMUNITY_SESSION_TIMEOUT_DATA, - }, - result: { - data: { - getCommunityData: { - timeout: 30, - }, - }, - }, - delay: 1000, - }, - { - request: { - query: REVOKE_REFRESH_TOKEN, - }, - error: new Error('Failed to revoke refresh token'), - }, - ]; - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - act(() => { - result.current.startSession(); - result.current.handleLogout(); - }); - - await waitFor(() => - expect(consoleErrorMock).toHaveBeenCalledWith( - 'Error revoking refresh token:', - expect.any(Error), - ), - ); - - consoleErrorMock.mockRestore(); - }); - - test('should set session timeout based on fetched data', async () => { - jest.spyOn(global, 'setTimeout'); - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - act(() => { - result.current.startSession(); - }); - - expect(global.setTimeout).toHaveBeenCalled(); - }); - - test('should call errorHandler on query error', async () => { - const errorMocks = [ - { - request: { - query: GET_COMMUNITY_SESSION_TIMEOUT_DATA, - }, - error: new Error('An error occurred'), - }, - ]; - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - act(() => { - result.current.startSession(); - }); - - await waitFor(() => expect(errorHandler).toHaveBeenCalled()); - }); - //dfghjkjhgfds - - test('should remove event listeners on endSession', async () => { - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - // Mock the removeEventListener functions for both window and document - const removeEventListenerMock = jest.fn(); - - // Temporarily replace the real methods with the mock - const originalWindowRemoveEventListener = window.removeEventListener; - const originalDocumentRemoveEventListener = document.removeEventListener; - - window.removeEventListener = removeEventListenerMock; - document.removeEventListener = removeEventListenerMock; - - // await waitForNextUpdate(); - - act(() => { - result.current.startSession(); - }); - - act(() => { - result.current.endSession(); - }); - - // Test that event listeners were removed - expect(removeEventListenerMock).toHaveBeenCalledWith( - 'mousemove', - expect.any(Function), - ); - expect(removeEventListenerMock).toHaveBeenCalledWith( - 'keydown', - expect.any(Function), - ); - expect(removeEventListenerMock).toHaveBeenCalledWith( - 'visibilitychange', - expect.any(Function), - ); - - // Restore the original removeEventListener functions - window.removeEventListener = originalWindowRemoveEventListener; - document.removeEventListener = originalDocumentRemoveEventListener; - }); - - test('should call initialize timers when session is still active when the user returns to the tab', async () => { - jest.useFakeTimers(); - jest.spyOn(global, 'setTimeout').mockImplementation(jest.fn()); - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }) => ( - - {children} - - ), - }); - - jest.advanceTimersByTime(1000); - - // Set initial visibility state to visible - Object.defineProperty(document, 'visibilityState', { - value: 'visible', - writable: true, - }); - - // Start the session - act(() => { - result.current.startSession(); - jest.advanceTimersByTime(10 * 60 * 1000); // Fast-forward - }); - - // Simulate the user leaving the tab (set visibility to hidden) - Object.defineProperty(document, 'visibilityState', { - value: 'hidden', - writable: true, - }); - - act(() => { - document.dispatchEvent(new Event('visibilitychange')); - }); - - // Fast-forward time by more than the session timeout - act(() => { - jest.advanceTimersByTime(5 * 60 * 1000); // Fast-forward - }); - - // Simulate the user returning to the tab - Object.defineProperty(document, 'visibilityState', { - value: 'visible', - writable: true, - }); - - act(() => { - document.dispatchEvent(new Event('visibilitychange')); - }); - - jest.advanceTimersByTime(1000); - - expect(global.setTimeout).toHaveBeenCalled(); - - // Restore real timers - jest.useRealTimers(); - }); - - test('should call handleLogout when session expires due to inactivity away from tab', async () => { - jest.useFakeTimers(); // Use fake timers to control time - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }) => ( - - {children} - - ), - }); - - jest.advanceTimersByTime(1000); - - // Set initial visibility state to visible - Object.defineProperty(document, 'visibilityState', { - value: 'visible', - writable: true, - }); - - // Start the session - act(() => { - result.current.startSession(); - jest.advanceTimersByTime(10 * 60 * 1000); // Fast-forward - }); - - // Simulate the user leaving the tab (set visibility to hidden) - Object.defineProperty(document, 'visibilityState', { - value: 'hidden', - writable: true, - }); - - act(() => { - document.dispatchEvent(new Event('visibilitychange')); - }); - - // Fast-forward time by more than the session timeout - act(() => { - jest.advanceTimersByTime(32 * 60 * 1000); // Fast-forward by 32 minutes - }); - - // Simulate the user returning to the tab - Object.defineProperty(document, 'visibilityState', { - value: 'visible', - writable: true, - }); - - act(() => { - document.dispatchEvent(new Event('visibilitychange')); - }); - - jest.advanceTimersByTime(250); - - await waitFor(() => { - expect(global.localStorage.clear).toHaveBeenCalled(); - expect(toast.warning).toHaveBeenCalledWith('sessionLogout', { - autoClose: false, - }); - }); - - // Restore real timers - jest.useRealTimers(); - }); - - test('should handle logout and revoke token', async () => { - jest.useFakeTimers(); - - const { result } = renderHook(() => useSession(), { - wrapper: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - }); - - act(() => { - result.current.startSession(); - result.current.handleLogout(); - }); - - await waitFor(() => { - expect(global.localStorage.clear).toHaveBeenCalled(); - expect(toast.warning).toHaveBeenCalledWith('sessionLogout', { - autoClose: false, - }); - }); - - jest.useRealTimers(); - }); -}); diff --git a/vitest.config.ts b/vitest.config.ts index c158cf9c2a..3d071e7534 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -31,6 +31,7 @@ export default defineConfig({ '**/*.d.ts', 'src/test/**', 'vitest.config.ts', + 'vitest.setup.ts', // Exclude from coverage if necessary ], reporter: ['text', 'html', 'text-summary', 'lcov'], },