From fd7603cd744a3593d936dbfb69a28ed9901247d8 Mon Sep 17 00:00:00 2001 From: Kanhaiya yadav <93936630+kanhaiya04@users.noreply.github.com> Date: Fri, 5 Jan 2024 18:55:25 +0530 Subject: [PATCH 1/5] Delete sample org bug fixed (#1257) * Delete sample org bug fixed #1217 * Added the test for the DeleteOrg component * Changed the color of the delete org modal header to standard green color * Successfully deleted sample org msg in make multilingual * created a async wait() function * passed 2000ms instead of adding wait twice --- public/locales/en.json | 3 +- public/locales/fr.json | 3 +- public/locales/hi.json | 3 +- public/locales/sp.json | 3 +- public/locales/zh.json | 3 +- src/components/DeleteOrg/DeleteOrg.test.tsx | 157 +++++++++++++++----- src/components/DeleteOrg/DeleteOrg.tsx | 8 +- 7 files changed, 134 insertions(+), 46 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 7f07275c54..c495927f98 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -431,7 +431,8 @@ "deleteMsg": "Do you want to delete this organization?", "cancel": "Cancel", "confirmDelete": "Confirm Delete", - "longDelOrgMsg": "By clicking on Delete Organization button the organization will be permanently deleted along with its events, tags and all related data." + "longDelOrgMsg": "By clicking on Delete Organization button the organization will be permanently deleted along with its events, tags and all related data.", + "successfullyDeletedSampleOrganization": "Successfully deleted sample Organization" }, "userUpdate": { "firstName": "First Name", diff --git a/public/locales/fr.json b/public/locales/fr.json index b626994c5c..43444a56f6 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -428,7 +428,8 @@ "deleteMsg": "Voulez-vous supprimer cette organisation ?", "cancel": "Annuler", "confirmDelete": "Confirmer la suppression", - "longDelOrgMsg": "En cliquant sur le bouton Supprimer l'organisation, l'organisation sera définitivement supprimée ainsi que ses événements, balises et toutes les données associées." + "longDelOrgMsg": "En cliquant sur le bouton Supprimer l'organisation, l'organisation sera définitivement supprimée ainsi que ses événements, balises et toutes les données associées.", + "successfullyDeletedSampleOrganization": "Exemple d'organisation supprimé avec succès" }, "userUpdate": { "firstName": "Prénom", diff --git a/public/locales/hi.json b/public/locales/hi.json index 5f272bad8f..c1455ac177 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -427,7 +427,8 @@ "deleteMsg": "क्या आप इस संगठन को हटाना चाहते हैं?", "cancel": "रद्द करना", "confirmDelete": "हटाने की पुष्टि करें", - "longDelOrgMsg": "संगठन को हमेशा के लिए हटा देने के लिए संगठन हटाने के बटन पर क्लिक करके, उसके इवेंट्स, टैग्स और सभी संबंधित डेटा सहित सभी जानकारी हटा दी जाएगी।" + "longDelOrgMsg": "संगठन को हमेशा के लिए हटा देने के लिए संगठन हटाने के बटन पर क्लिक करके, उसके इवेंट्स, टैग्स और सभी संबंधित डेटा सहित सभी जानकारी हटा दी जाएगी।", + "successfullyDeletedSampleOrganization": "नमूना संगठन सफलतापूर्वक हटा दिया गया" }, "userUpdate": { "firstName": "पहला नाम", diff --git a/public/locales/sp.json b/public/locales/sp.json index abf86c60dc..90223526f3 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -427,7 +427,8 @@ "deleteMsg": "¿Desea eliminar esta organización?", "cancel": "Cancelar", "confirmDelete": "Confirmar eliminación", - "longDelOrgMsg": "Al hacer clic en el botón Eliminar organización, la organización se eliminará permanentemente junto con sus eventos, etiquetas y todos los datos relacionados." + "longDelOrgMsg": "Al hacer clic en el botón Eliminar organización, la organización se eliminará permanentemente junto con sus eventos, etiquetas y todos los datos relacionados.", + "successfullyDeletedSampleOrganization": "Organización de muestra eliminada correctamente" }, "userUpdate": { "firstName": "Primer nombre", diff --git a/public/locales/zh.json b/public/locales/zh.json index 30a048d1be..58061a3c85 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -427,7 +427,8 @@ "deleteMsg": "您是否要删除此组织?", "cancel": "取消", "confirmDelete": "确认删除", - "longDelOrgMsg": "点击删除组织按钮,组织将被永久删除,包括其事件、标签和所有相关数据。" + "longDelOrgMsg": "点击删除组织按钮,组织将被永久删除,包括其事件、标签和所有相关数据。", + "successfullyDeletedSampleOrganization": "已成功刪除樣本組織" }, "userUpdate": { "firstName": "名", diff --git a/src/components/DeleteOrg/DeleteOrg.test.tsx b/src/components/DeleteOrg/DeleteOrg.test.tsx index e0a58351f7..f3cb9b492d 100644 --- a/src/components/DeleteOrg/DeleteOrg.test.tsx +++ b/src/components/DeleteOrg/DeleteOrg.test.tsx @@ -1,39 +1,103 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { render, screen } 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 } from 'GraphQl/Mutations/mutations'; +import { + DELETE_ORGANIZATION_MUTATION, + REMOVE_SAMPLE_ORGANIZATION_MUTATION, +} from 'GraphQl/Mutations/mutations'; import { act } from 'react-dom/test-utils'; import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; import DeleteOrg from './DeleteOrg'; +import { ToastContainer, toast } from 'react-toastify'; +import { IS_SAMPLE_ORGANIZATION_QUERY } from 'GraphQl/Queries/Queries'; + +async function wait(ms = 1000): Promise { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, ms)); + }); +} const MOCKS = [ + { + request: { + query: IS_SAMPLE_ORGANIZATION_QUERY, + variables: { + isSampleOrganizationId: '123', + }, + }, + result: { + data: { + isSampleOrganization: true, + }, + }, + }, + { + request: { + query: REMOVE_SAMPLE_ORGANIZATION_MUTATION, + }, + result: { + data: { + removeSampleOrganization: true, + }, + }, + }, { request: { query: DELETE_ORGANIZATION_MUTATION, variables: { - id: 123, + id: '456', }, }, result: { data: { - removeOrganization: [ - { - _id: 123, - }, - ], + removeOrganization: { + _id: '456', + }, }, }, }, ]; +const MOCKS_WITH_ERROR = [ + { + request: { + query: IS_SAMPLE_ORGANIZATION_QUERY, + variables: { + isSampleOrganizationId: '123', + }, + }, + result: { + data: { + isSampleOrganization: true, + }, + }, + }, + { + request: { + query: DELETE_ORGANIZATION_MUTATION, + variables: { + id: '456', + }, + }, + error: new Error('Failed to delete organization'), + }, + { + request: { + query: REMOVE_SAMPLE_ORGANIZATION_MUTATION, + }, + error: new Error('Failed to delete sample organization'), + }, +]; + const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_WITH_ERROR, true); afterEach(() => { localStorage.clear(); @@ -41,29 +105,31 @@ afterEach(() => { describe('Delete Organization Component', () => { test('should be able to Toggle Delete Organization Modal', async () => { - window.location.assign('/orgsetting/id=123'); + window.location.assign('/orgsetting/id=456'); localStorage.setItem('UserType', 'SUPERADMIN'); render( + ); + await wait(); screen.getByTestId(/openDeleteModalBtn/i).click(); expect(screen.getByTestId(/orgDeleteModal/i)).toBeInTheDocument(); screen.getByTestId(/closeDelOrgModalBtn/i).click(); await act(async () => { expect(screen.queryByTestId(/orgDeleteModal/i)).not.toHaveFocus(); }); - expect(window.location).toBeAt('/orgsetting/id=123'); + expect(window.location).toBeAt('/orgsetting/id=456'); }); - test('Delete organization functionality should work properly', async () => { + test('should be able to Toggle Delete Organization Modal When Organization is Sample Organization', async () => { window.location.assign('/orgsetting/id=123'); localStorage.setItem('UserType', 'SUPERADMIN'); render( @@ -71,18 +137,25 @@ describe('Delete Organization Component', () => { + ); + await wait(); screen.getByTestId(/openDeleteModalBtn/i).click(); - screen.getByTestId(/deleteOrganizationBtn/i).click(); - expect(window.location).not.toBeNull(); + expect(screen.getByTestId(/orgDeleteModal/i)).toBeInTheDocument(); + screen.getByTestId(/closeDelOrgModalBtn/i).click(); + await act(async () => { + expect(screen.queryByTestId(/orgDeleteModal/i)).not.toHaveFocus(); + }); + expect(window.location).toBeAt('/orgsetting/id=123'); }); - test('should handle deletion failure gracefully', async () => { - window.location.assign('/orgsetting/id=456'); // Using an ID that triggers a failure + + test('Delete organization functionality should work properly', async () => { + window.location.assign('/orgsetting/id=456'); localStorage.setItem('UserType', 'SUPERADMIN'); render( @@ -95,12 +168,14 @@ describe('Delete Organization Component', () => { ); - - fireEvent.click(screen.getByTestId('openDeleteModalBtn')); - fireEvent.click(screen.getByTestId('deleteOrganizationBtn')); - expect(screen.queryByText(/Deletion failed!/i)).toBeNull(); + await wait(); + screen.getByTestId(/openDeleteModalBtn/i).click(); + screen.getByTestId(/deleteOrganizationBtn/i).click(); + await wait(); + expect(window.location.replace).toHaveBeenCalledWith('/orglist'); }); - test('should close the Delete Organization Modal when "Cancel" button is clicked', async () => { + + test('Delete organization functionality should work properly for sample org', async () => { window.location.assign('/orgsetting/id=123'); localStorage.setItem('UserType', 'SUPERADMIN'); render( @@ -114,19 +189,19 @@ describe('Delete Organization Component', () => { ); - fireEvent.click(screen.getByTestId('openDeleteModalBtn')); - expect(screen.getByTestId('orgDeleteModal')).toBeInTheDocument(); - fireEvent.click(screen.getByTestId('closeDelOrgModalBtn')); - await waitFor(() => { - expect(screen.queryByTestId('orgDeleteModal')).toBeNull(); - }); - expect(window.location).toBeAt('/orgsetting/id=123'); + await wait(); + screen.getByTestId(/openDeleteModalBtn/i).click(); + screen.getByTestId(/deleteOrganizationBtn/i).click(); + await wait(2000); + expect(window.location.replace).toHaveBeenCalledWith('/orglist'); }); - test('should open the Delete Organization Modal when "Delete" button is clicked', async () => { + + test('Error handling for IS_SAMPLE_ORGANIZATION_QUERY mock', async () => { window.location.assign('/orgsetting/id=123'); localStorage.setItem('UserType', 'SUPERADMIN'); + jest.spyOn(toast, 'error'); render( - + @@ -136,15 +211,20 @@ describe('Delete Organization Component', () => { ); - fireEvent.click(screen.getByTestId('openDeleteModalBtn')); - expect(screen.getByTestId('orgDeleteModal')).toBeInTheDocument(); - expect(window.location).toBeAt('/orgsetting/id=123'); + await wait(); + screen.getByTestId(/openDeleteModalBtn/i).click(); + screen.getByTestId(/deleteOrganizationBtn/i).click(); + await wait(); + expect(toast.error).toHaveBeenCalledWith( + 'Failed to delete sample organization' + ); }); - test('render Delete Organization Modal when "Delete" button is clicked', async () => { - window.location.assign('/orgsetting/id=123'); + + test('Error handling for DELETE_ORGANIZATION_MUTATION mock', async () => { + window.location.assign('/orgsetting/id=456'); localStorage.setItem('UserType', 'SUPERADMIN'); render( - + @@ -154,8 +234,9 @@ describe('Delete Organization Component', () => { ); - fireEvent.click(screen.getByTestId('openDeleteModalBtn')); - expect(screen.getByTestId('orgDeleteModal')).toBeInTheDocument(); - expect(window.location).toBeAt('/orgsetting/id=123'); + await wait(); + screen.getByTestId(/openDeleteModalBtn/i).click(); + screen.getByTestId(/deleteOrganizationBtn/i).click(); + await wait(); }); }); diff --git a/src/components/DeleteOrg/DeleteOrg.tsx b/src/components/DeleteOrg/DeleteOrg.tsx index b7ceafbd5c..72fcf5ac03 100644 --- a/src/components/DeleteOrg/DeleteOrg.tsx +++ b/src/components/DeleteOrg/DeleteOrg.tsx @@ -33,12 +33,14 @@ function deleteOrg(): JSX.Element { if (data && data.isSampleOrganization) { removeSampleOrganization() .then(() => { - toast.success('Successfully deleted sample Organization'); + toast.success(t('successfullyDeletedSampleOrganization')); + setTimeout(() => { + window.location.replace('/orglist'); + }, 1000); }) .catch((error) => { toast.error(error.message); }); - window.location.replace('/orglist'); } else { try { await del({ @@ -82,7 +84,7 @@ function deleteOrg(): JSX.Element { onHide={toggleDeleteModal} data-testid="orgDeleteModal" > - +
{t('deleteOrganization')}
{t('deleteMsg')} From 6ba8db08085157e09cbb71d5ff14da7d7b5acec7 Mon Sep 17 00:00:00 2001 From: Alok Gupta Date: Fri, 5 Jan 2024 18:57:02 +0530 Subject: [PATCH 2/5] Added the palisadoes foundation link to logo (#1373) --- src/screens/LoginPage/LoginPage.tsx | 10 ++++++-- .../UserLoginPage/UserLoginPage.tsx | 23 ++++++++++++------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/screens/LoginPage/LoginPage.tsx b/src/screens/LoginPage/LoginPage.tsx index 44afdbacb9..67f5e0301e 100644 --- a/src/screens/LoginPage/LoginPage.tsx +++ b/src/screens/LoginPage/LoginPage.tsx @@ -233,8 +233,14 @@ function loginPage(): JSX.Element {
- -

{t('fromPalisadoes')}

+ + +

{t('fromPalisadoes')}

+
diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx index 559d088a9e..e8aa947d7c 100644 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx @@ -29,14 +29,21 @@ export default function userLoginPage(): JSX.Element { return (
- Palisadoes Branding -
-

{t('fromPalisadoes')}

-
+ + Palisadoes Branding + +
+

{t('fromPalisadoes')}

+
+
Date: Sat, 6 Jan 2024 19:06:52 +0530 Subject: [PATCH 3/5] Changed User Login and User Register component (#1368) * Added tests for Advertisements.tsx * Added tests for Advertisement.tsx * Added Filtering Functionality * fixed tests * changed name of test * Changed the user login and register portal to be consistent * Added tests --- public/locales/en.json | 30 + public/locales/fr.json | 30 + public/locales/hi.json | 30 + public/locales/sp.json | 30 + public/locales/zh.json | 30 + .../UserLoginPage/UserLoginPage.module.css | 202 ++++- .../UserLoginPage/UserLoginPage.test.tsx | 607 ++++++++++++++- .../UserLoginPage/UserLoginPage.tsx | 699 +++++++++++++++--- 8 files changed, 1523 insertions(+), 135 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index c495927f98..e9ac82fc34 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -28,6 +28,36 @@ "login_to_admin_portal": "Admin Portal Login", "OR": "OR" }, + "userLoginPage": { + "title": "Talawa Admin", + "fromPalisadoes": "An open source application by Palisadoes Foundation volunteers", + "talawa_portal": "Talawa Admin Portal", + "login": "Login", + "register": "Register", + "firstName": "First Name", + "lastName": "Last Name", + "email": "Email", + "password": "Password", + "atleast_8_char_long": "Atleast 8 Character long", + "Password_and_Confirm_password_mismatches.": "Password and Confirm password mismatches.", + "confirmPassword": "Confirm Password", + "forgotPassword": "Forgot Password ?", + "enterEmail": "Enter Email", + "enterPassword": "Enter Password", + "doNotOwnAnAccount": "Do not own an account?", + "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too.", + "captchaError": "Captcha Error!", + "Please_check_the_captcha": "Please, check the captcha.", + "Something_went_wrong": "Something went wrong, Please try after sometime.", + "passwordMismatches": "Password and Confirm password mismatches.", + "fillCorrectly": "Fill all the Details Correctly.", + "notAuthorised": "Sorry! you are not Authorised!", + "notFound": "User not found!", + "successfullyRegistered": "Successfully Registered. Please wait until you will be approved.", + "userLogin": "User Login", + "afterRegister": "Successfully registered. Please wait for admin to approve your request.", + "OR": "OR" + }, "latestEvents": { "eventCardTitle": "Upcoming Events", "eventCardSeeAll": "See All", diff --git a/public/locales/fr.json b/public/locales/fr.json index 43444a56f6..09d3262932 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -28,6 +28,36 @@ "login_to_admin_portal": "Connexion à l'administration du portail", "OR": "OU" }, + "userLoginPage": { + "title": "Administrateur Talawa", + "talawa_portal": "Portail D'Administrateur Talawa", + "fromPalisadoes": "Une application open source par les volontaires de la Fondation Palissades", + "login": "Connexion", + "register": "S'inscrire", + "firstName": "Prénom", + "lastName": "Nom de famille", + "email": "E-mail", + "password": "Mot de passe", + "atleast_8_char_long": "Au moins 8 caractères", + "Password_and_Confirm_password_mismatches.": "Le mot de passe et la confirmation du mot de passe ne correspondent pas.", + "confirmPassword": "Confirmez le mot de passe", + "forgotPassword": "Mot de passe oublié ?", + "enterEmail": "entrez l'e-mail", + "enterPassword": "Entrer le mot de passe", + "doNotOwnAnAccount": "Vous n'avez pas de compte ?", + "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau.", + "captchaError": "Erreur de captcha !", + "Please_check_the_captcha": "Veuillez vérifier le captcha.", + "Something_went_wrong": "Quelque chose s'est mal passé, veuillez réessayer plus tard.", + "passwordMismatches": "Le mot de passe et la confirmation du mot de passe ne correspondent pas.", + "fillCorrectly": "Remplissez tous les détails correctement.", + "notAuthorised": "Désolé ! vous n'êtes pas autorisé !", + "notFound": "Utilisateur introuvable !", + "successfullyRegistered": "Enregistré avec succès. Veuillez patienter jusqu'à ce que vous soyez approuvé.", + "userLogin": "Utilisateur en ligne", + "afterRegister": "Enregistré avec succès. Veuillez attendre que l'administrateur approuve votre demande.", + "OR": "OU" + }, "latestEvents": { "eventCardTitle": "Événements à venir", "eventCardSeeAll": "Voir Tout", diff --git a/public/locales/hi.json b/public/locales/hi.json index c1455ac177..2b5bd0db60 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -28,6 +28,36 @@ "login_to_admin_portal": "एडमिन पोर्टल लॉगिन", "OR": "या" }, + "userLoginPage": { + "title": "तलवा व्यवस्थापक", + "fromPalisadoes": "पलिसाडो के स्वयंसेवकों द्वारा एक खुला स्रोत अनुप्रयोग", + "talawa_portal": "तलावा प्रशासन पोर्टल", + "login": "लॉग इन करें", + "register": "पंजीकरण करवाना", + "firstName": "पहला नाम", + "lastName": "उपनाम", + "email": "ईमेल", + "password": "पासवर्ड", + "atleast_8_char_long": "कम से कम 8 कैरेक्टर लंबा", + "Password_and_Confirm_password_mismatches.": "पासवर्ड और पुष्टि पासवर्ड बेमेल।", + "confirmPassword": "पासवर्ड की पुष्टि कीजिये", + "forgotPassword": "पासवर्ड भूल गए ?", + "enterEmail": "ईमेल दर्ज करें", + "enterPassword": "पास वर्ड दर्ज करें", + "doNotOwnAnAccount": "क्या आपके पास खाता नहीं है?", + "talawaApiUnavailable": "तलावा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रही है? अपनी नेटवर्क कनेक्टिविटी की भी जाँच करें।", + "captchaError": "कैप्चा त्रुटि!", + "Please_check_the_captcha": "कृपया, कैप्चा जांचें।", + "Something_went_wrong": "कुछ गलत हुआ, कृपया कुछ समय बाद प्रयास करें।", + "passwordMismatches": "पासवर्ड और पुष्टि पासवर्ड मेल नहीं खाते।", + "fillCorrectly": "सभी विवरण सही ढंग से भरें।", + "notAuthorised": "क्षमा करें! आप अधिकृत नहीं हैं!", + "notFound": "उपयोगकर्ता नहीं मिला!", + "successfullyRegistered": "सफलतापूर्वक पंजीकृत। कृपया स्वीकृत होने तक प्रतीक्षा करें।", + "afterRegister": "पंजीकरण सफलतापूर्वक हो गया है। कृपया आपके अनुरोध को स्वीकार करने के लिए व्यवस्थापक की प्रतीक्षा करें।", + "userLogin": "उपयोगकर्ता लॉगिन", + "OR": "या" + }, "latestEvents": { "eventCardTitle": "आगामी घटनाएँ", "eventCardSeeAll": "सभी देखें", diff --git a/public/locales/sp.json b/public/locales/sp.json index 90223526f3..95e8dfb169 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -28,6 +28,36 @@ "login_to_admin_portal": "Inicio de sesión en el portal de administración", "OR": "O" }, + "userLoginPage": { + "title": "Administrador Talawa", + "fromPalisadoes": "Una aplicación de código abierto de los voluntarios de la Fundación palisados", + "talawa_portal": "Portal De Administración Talawa", + "login": "Acceso", + "register": "Registro", + "firstName": "Primer nombre", + "lastName": "Apellido", + "email": "Correo electrónico", + "password": "Clave", + "atleast_8_char_long": "Al menos 8 caracteres de largo", + "Password_and_Confirm_password_mismatches.": "Contraseña y Confirmar contraseña no coinciden.", + "confirmPassword": "Confirmar contraseña", + "forgotPassword": "Has olvidado tu contraseña ?", + "enterEmail": "ingrese correo electrónico", + "enterPassword": "introducir la contraseña", + "doNotOwnAnAccount": "¿No tienes una cuenta?", + "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Verifica también la conectividad de tu red.", + "captchaError": "¡Error de captcha!", + "Please_check_the_captcha": "Por favor, revisa el captcha.", + "Something_went_wrong": "Algo salió mal. Inténtalo después de un tiempo", + "passwordMismatches": "Contraseña y Confirmar contraseña no coinciden.", + "fillCorrectly": "Complete todos los detalles correctamente.", + "notAuthorised": "¡Lo siento! ¡No estás autorizado!", + "notFound": "¡Usuario no encontrado!", + "successfullyRegistered": "Registrado con éxito. Espere hasta que sea aprobado", + "userLogin": "Inicio de sesión de usuario", + "afterRegister": "Registrado exitosamente. Espere a que el administrador apruebe su solicitud.", + "OR": "O" + }, "latestEvents": { "eventCardTitle": "Próximos Eventos", "eventCardSeeAll": "Ver Todos", diff --git a/public/locales/zh.json b/public/locales/zh.json index 58061a3c85..7838e05f5a 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -28,6 +28,36 @@ "login_to_admin_portal": " 管理员门户登录 ", "OR": "或者" }, + "userLoginPage": { + "title": "塔拉瓦管理員", + "fromPalisadoes": "柵欄 基金会志愿者的开源应用程序", + "talawa_portal": "塔拉瓦管理門戶", + "login": "登錄", + "register": "登記", + "firstName": "名", + "lastName": "姓", + "email": "電子郵件", + "password": "密碼", + "atleast_8_char_long": "至少 8 個字符長", + "Password_and_Confirm_password_mismatches.": "密碼和確認密碼不匹配。", + "confirmPassword": "確認密碼", + "forgotPassword": "忘記密碼 ?", + "enterEmail": "输入电子邮件", + "enterPassword": "输入密码", + "doNotOwnAnAccount": "沒有帳戶嗎?", + "talawaApiUnavailable": "服務不可用。它正在運行嗎?還要檢查您的網絡連接。", + "captchaError": "驗證碼錯誤!", + "Please_check_the_captcha": "請檢查驗證碼。", + "Something_went_wrong": "出了點問題,請稍後再試。", + "passwordMismatches": "密碼和確認密碼不匹配。", + "fillCorrectly": "正確填寫所有細節。", + "notAuthorised": "抱歉!你沒有被授權!", + "notFound": "找不到用戶!", + "successfullyRegistered": "註冊成功,請等待審核通過。", + "userLogin": "用户登录", + "afterRegister": "註冊成功。 請等待管理員批准您的請求。", + "OR": "或者" + }, "latestEvents": { "eventCardTitle": "即将举行的活动", "eventCardSeeAll": "查看全部", diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css b/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css index 32858723ef..02cd04032a 100644 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css @@ -1,56 +1,200 @@ -body::before { - content: none !important; +.login_background { + min-height: 100vh; } -.leftPane { +.row .left_portion { + display: flex; + justify-content: center; align-items: center; - width: 60%; - min-width: 300px; + flex-direction: column; + height: 100vh; +} + +.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); } -.palisadoesImage { - width: 100%; - height: auto; - max-width: 700px; +.row .right_portion::-webkit-scrollbar { + display: none; } -.talawaImage { - width: 40%; - height: auto; - margin-left: 50%; - transform: translateX(-50%); +.row .right_portion .langChangeBtn { + margin: 0; + position: absolute; + top: 1rem; + left: 2rem; } -.mainContainer { - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 10px; - min-height: 100vh; +.row .right_portion .talawa_logo { + height: 150px; + width: 150px; + display: block; + margin: 1rem auto; + -webkit-animation: zoomIn 0.3s ease-in-out; + animation: zoomIn 0.3s ease-in-out; } -.contentContainer { - flex-grow: 1; - display: flex; - flex-direction: column; - justify-content: center; - padding: 20px 50px; - background-color: var(--bs-white); +.row .orText { + display: block; + position: absolute; + top: calc(-0.7rem - 0.5rem); + left: calc(50% - 2.6rem); + margin: 0 auto; + padding: 0.5rem 2rem; + z-index: 100; + background: var(--bs-white); + color: var(--bs-secondary); } -@media only screen and (max-width: 800px) { - .leftPane { +@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; + } +} + +.active_tab { + -webkit-animation: fadeIn 0.3s ease-in-out; + animation: fadeIn 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); + } +} + +@-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); + } +} + .socialIcons { display: flex; gap: 16px; diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx index d60e37c682..7b41dad875 100644 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx @@ -1,14 +1,83 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render } from '@testing-library/react'; -import { I18nextProvider } from 'react-i18next'; +import { act, render, screen } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import { store } from 'state/store'; +import userEvent from '@testing-library/user-event'; +import { I18nextProvider } from 'react-i18next'; +import 'jest-localstorage-mock'; +import 'jest-location-mock'; import { StaticMockLink } from 'utils/StaticMockLink'; +import LoginPage from './UserLoginPage'; +import { + LOGIN_MUTATION, + RECAPTCHA_MUTATION, + SIGNUP_MUTATION, +} from 'GraphQl/Mutations/mutations'; +import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; -import UserLoginPage from './UserLoginPage'; +const MOCKS = [ + { + request: { + query: LOGIN_MUTATION, + variables: { + email: 'johndoe@gmail.com', + password: 'johndoe', + }, + }, + result: { + data: { + login: { + user: { + _id: '1', + userType: 'ADMIN', + adminApproved: true, + }, + accessToken: 'accessToken', + refreshToken: 'refreshToken', + }, + }, + }, + }, + { + request: { + query: SIGNUP_MUTATION, + variables: { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + password: 'johnDoe', + }, + }, + result: { + data: { + register: { + user: { + _id: '1', + }, + accessToken: 'accessToken', + refreshToken: 'refreshToken', + }, + }, + }, + }, + { + request: { + query: RECAPTCHA_MUTATION, + variables: { + recaptchaToken: null, + }, + }, + result: { + data: { + recaptcha: true, + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); async function wait(ms = 100): Promise { await act(() => { @@ -18,22 +87,544 @@ async function wait(ms = 100): Promise { }); } -const link = new StaticMockLink([], true); +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +jest.mock('Constant/constant.ts', () => ({ + ...jest.requireActual('Constant/constant.ts'), + REACT_APP_USE_RECAPTCHA: 'yes', + RECAPTCHA_SITE_KEY: 'xxx', +})); + +describe('Talawa-API server fetch check', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('Checks if Talawa-API resource is loaded successfully', async () => { + global.fetch = jest.fn(() => Promise.resolve({} as unknown as Response)); + + await act(async () => { + render( + + + + + + + + + + ); + }); + + expect(fetch).toHaveBeenCalledWith('http://localhost:4000/graphql/'); + }); + + test('displays warning message when resource loading fails', async () => { + const mockError = new Error('Network error'); + global.fetch = jest.fn(() => Promise.reject(mockError)); + + await act(async () => { + render( + + + + + + + + + + ); + }); + + expect(fetch).toHaveBeenCalledWith('http://localhost:4000/graphql/'); + }); +}); + +describe('Testing Login Page Screen', () => { + test('Component Should be rendered properly', async () => { + window.location.assign('/user/organizations'); + + render( + + + + + + + + + + ); + + await wait(); + + expect(screen.getByText(/User Login/i)).toBeInTheDocument(); + expect(window.location).toBeAt('/user/organizations'); + }); + + test('Testing registration functionality', async () => { + const formData = { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + password: 'johndoe', + confirmPassword: 'johndoe', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); + + await wait(); + + userEvent.type( + screen.getByPlaceholderText(/First Name/i), + formData.firstName + ); + userEvent.type( + screen.getByPlaceholderText(/Last name/i), + formData.lastName + ); + userEvent.type(screen.getByTestId(/signInEmail/i), formData.email); + userEvent.type(screen.getByPlaceholderText('Password'), formData.password); + userEvent.type( + screen.getByPlaceholderText('Confirm Password'), + formData.confirmPassword + ); + + userEvent.click(screen.getByTestId('registrationBtn')); + }); + + test('Testing registration functionality, when password and confirm password is not same', async () => { + const formData = { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + password: 'johndoe', + confirmPassword: 'doeJohn', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); + + userEvent.type( + screen.getByPlaceholderText(/First Name/i), + formData.firstName + ); + userEvent.type( + screen.getByPlaceholderText(/Last Name/i), + formData.lastName + ); + userEvent.type(screen.getByTestId(/signInEmail/i), formData.email); + userEvent.type(screen.getByPlaceholderText('Password'), formData.password); + userEvent.type( + screen.getByPlaceholderText('Confirm Password'), + formData.confirmPassword + ); + + userEvent.click(screen.getByTestId('registrationBtn')); + }); + + test('Testing registration functionality, when input is not filled correctly', async () => { + const formData = { + firstName: 'J', + lastName: 'D', + email: 'johndoe@gmail.com', + password: 'joe', + confirmPassword: 'joe', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); + + userEvent.type( + screen.getByPlaceholderText(/First Name/i), + formData.firstName + ); + userEvent.type( + screen.getByPlaceholderText(/Last Name/i), + formData.lastName + ); + userEvent.type(screen.getByTestId(/signInEmail/i), formData.email); + userEvent.type(screen.getByPlaceholderText('Password'), formData.password); + userEvent.type( + screen.getByPlaceholderText('Confirm Password'), + formData.confirmPassword + ); + + userEvent.click(screen.getByTestId('registrationBtn')); + }); + + test('switches to login tab on successful registration', async () => { + const formData = { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + password: 'johndoe', + confirmPassword: 'johndoe', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); + userEvent.type( + screen.getByPlaceholderText(/First Name/i), + formData.firstName + ); + userEvent.type( + screen.getByPlaceholderText(/Last name/i), + formData.lastName + ); + userEvent.type(screen.getByTestId(/signInEmail/i), formData.email); + userEvent.type(screen.getByPlaceholderText('Password'), formData.password); + userEvent.type( + screen.getByPlaceholderText('Confirm Password'), + formData.confirmPassword + ); + + userEvent.click(screen.getByTestId('registrationBtn')); + + await wait(); + + // Check if the login tab is now active by checking for elements that only appear in the login tab + expect(screen.getByTestId('loginBtn')).toBeInTheDocument(); + expect(screen.getByTestId('goToRegisterPortion')).toBeInTheDocument(); + }); + + test('Testing toggle login register portion', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('goToRegisterPortion')); + + userEvent.click(screen.getByTestId('goToLoginPortion')); + + await wait(); + }); + + test('Testing login functionality', async () => { + const formData = { + email: 'johndoe@gmail.com', + password: 'johndoe', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId(/loginEmail/i), formData.email); + userEvent.type( + screen.getByPlaceholderText(/Enter Password/i), + formData.password + ); + + userEvent.click(screen.getByTestId('loginBtn')); + + await wait(); + }); + + test('Testing password preview feature for login', async () => { + render( + + + + + + + + + + ); + + await wait(); + + const input = screen.getByTestId('password') as HTMLInputElement; + const toggleText = screen.getByTestId('showLoginPassword'); + // password should be hidden + expect(input.type).toBe('password'); + // click the toggle button to show password + userEvent.click(toggleText); + expect(input.type).toBe('text'); + // click the toggle button to hide password + userEvent.click(toggleText); + expect(input.type).toBe('password'); + + await wait(); + }); + + test('Testing password preview feature for register', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('goToRegisterPortion')); + + const input = screen.getByTestId('passwordField') as HTMLInputElement; + const toggleText = screen.getByTestId('showPassword'); + // password should be hidden + expect(input.type).toBe('password'); + // click the toggle button to show password + userEvent.click(toggleText); + expect(input.type).toBe('text'); + // click the toggle button to hide password + userEvent.click(toggleText); + expect(input.type).toBe('password'); + + await wait(); + }); + + test('Testing confirm password preview feature', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('goToRegisterPortion')); + + const input = screen.getByTestId('cpassword') as HTMLInputElement; + const toggleText = screen.getByTestId('showPasswordCon'); + // password should be hidden + expect(input.type).toBe('password'); + // click the toggle button to show password + userEvent.click(toggleText); + expect(input.type).toBe('text'); + // click the toggle button to hide password + userEvent.click(toggleText); + expect(input.type).toBe('password'); + + await wait(); + }); + + test('Testing for the password error warning when user firsts lands on a page', async () => { + render( + + + + + + + + + + ); + await wait(); + + expect(screen.queryByTestId('passwordCheck')).toBeNull(); + }); + + test('Testing for the password error warning when user clicks on password field and password is less than 8 character', async () => { + const password = { + password: '7', + }; -describe('Testing User Login Page Screen [User Portal]', () => { - test('Screen should be rendered properly', async () => { render( - + ); + await wait(); + + userEvent.click(screen.getByTestId('goToRegisterPortion')); + + userEvent.type(screen.getByPlaceholderText('Password'), password.password); + + expect(screen.getByTestId('passwordField')).toHaveFocus(); + + expect(password.password.length).toBeLessThan(8); + expect(screen.queryByTestId('passwordCheck')).toBeInTheDocument(); + }); + + test('Testing for the password error warning when user clicks on password field and password is greater than or equal to 8 character', async () => { + const password = { + password: '12345678', + }; + + render( + + + + + + + + + + ); await wait(); + + userEvent.click(screen.getByTestId('goToRegisterPortion')); + + userEvent.type(screen.getByPlaceholderText('Password'), password.password); + + expect(screen.getByTestId('passwordField')).toHaveFocus(); + + expect(password.password.length).toBeGreaterThanOrEqual(8); + + expect(screen.queryByTestId('passwordCheck')).toBeNull(); + }); + + test('Testing for the password error warning when user clicks on fields except password field and password is less than 8 character', async () => { + const password = { + password: '7', + }; + + render( + + + + + + + + + + ); + await wait(); + + userEvent.click(screen.getByTestId('goToRegisterPortion')); + + expect(screen.getByPlaceholderText('Password')).not.toHaveFocus(); + + userEvent.type(screen.getByPlaceholderText('Password'), password.password); + + expect(password.password.length).toBeLessThan(8); + + expect(screen.queryByTestId('passwordCheck')).toBeInTheDocument(); + }); + + test('Testing for the password error warning when user clicks on fields except password field and password is greater than or equal to 8 character', async () => { + const password = { + password: '12345678', + }; + + render( + + + + + + + + + + ); + await wait(); + + userEvent.click(screen.getByTestId('goToRegisterPortion')); + + await wait(); + + expect(screen.getByPlaceholderText('Password')).not.toHaveFocus(); + + userEvent.type(screen.getByPlaceholderText('Password'), password.password); + + expect(password.password.length).toBeGreaterThanOrEqual(8); + + expect(screen.queryByTestId('passwordCheck')).toBeNull(); }); }); diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx index e8aa947d7c..4b6c36157f 100644 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx @@ -1,12 +1,14 @@ -import React from 'react'; +import { useMutation } from '@apollo/client'; +import type { ChangeEvent } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { Form } from 'react-bootstrap'; +import Button from 'react-bootstrap/Button'; +import Col from 'react-bootstrap/Col'; +import Row from 'react-bootstrap/Row'; +import ReCAPTCHA from 'react-google-recaptcha'; import { useTranslation } from 'react-i18next'; - -import PalisadoesImage from 'assets/images/palisadoes_logo.png'; -import TalawaImage from 'assets/images/talawa-logo-200x200.png'; -import ChangeLanguageDropDown from 'components/ChangeLanguageDropdown/ChangeLanguageDropDown'; -import Login from 'components/UserPortal/Login/Login'; -import Register from 'components/UserPortal/Register/Register'; -import styles from './UserLoginPage.module.css'; +import { Link, useHistory } from 'react-router-dom'; +import { toast } from 'react-toastify'; import { FacebookLogo, @@ -18,100 +20,601 @@ import { YoutubeLogo, } from 'assets/svgs/social-icons'; -export default function userLoginPage(): JSX.Element { - const { t } = useTranslation('translation', { keyPrefix: 'loginPage' }); +import { REACT_APP_USE_RECAPTCHA, RECAPTCHA_SITE_KEY } from 'Constant/constant'; +import { + LOGIN_MUTATION, + RECAPTCHA_MUTATION, + SIGNUP_MUTATION, +} from 'GraphQl/Mutations/mutations'; +import { ReactComponent as TalawaLogo } from 'assets/svgs/talawa.svg'; +import { ReactComponent as PalisadoesLogo } from 'assets/svgs/palisadoes.svg'; +import ChangeLanguageDropDown from 'components/ChangeLanguageDropdown/ChangeLanguageDropDown'; +import Loader from 'components/Loader/Loader'; +import { errorHandler } from 'utils/errorHandler'; +import styles from './UserLoginPage.module.css'; +import EmailOutlinedIcon from '@mui/icons-material/EmailOutlined'; + +function loginPage(): JSX.Element { + const { t } = useTranslation('translation', { keyPrefix: 'userLoginPage' }); + const history = useHistory(); + + document.title = t('title'); + + const [showTab, setShowTab] = useState<'LOGIN' | 'REGISTER'>('LOGIN'); + const [componentLoader, setComponentLoader] = useState(true); + const [isInputFocused, setIsInputFocused] = useState(false); + const [signformState, setSignFormState] = useState({ + signfirstName: '', + signlastName: '', + signEmail: '', + signPassword: '', + cPassword: '', + }); + const [formState, setFormState] = useState({ + email: '', + password: '', + }); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = + useState(false); + const recaptchaRef = useRef(null); + + useEffect(() => { + const isLoggedIn = localStorage.getItem('IsLoggedIn'); + if (isLoggedIn == 'TRUE') { + history.push('/user/organizations/'); + } + setComponentLoader(false); + }, []); - const [currentMode, setCurrentMode] = React.useState('login'); - const loginRegisterProps = { - setCurrentMode: setCurrentMode, + const togglePassword = (): void => setShowPassword(!showPassword); + const toggleConfirmPassword = (): void => + setShowConfirmPassword(!showConfirmPassword); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [login, { loading: loginLoading }] = useMutation(LOGIN_MUTATION); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [signup, { loading: signinLoading }] = useMutation(SIGNUP_MUTATION); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [recaptcha, { loading: recaptchaLoading }] = + useMutation(RECAPTCHA_MUTATION); + + useEffect(() => { + async function loadResource(): Promise { + try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const response = await fetch('http://localhost:4000/graphql/'); + } catch (error: any) { + /* istanbul ignore next */ + errorHandler(t, error); + } + } + + loadResource(); + }, []); + + const verifyRecaptcha = async ( + recaptchaToken: any + ): Promise => { + try { + /* istanbul ignore next */ + if (REACT_APP_USE_RECAPTCHA !== 'yes') { + return true; + } + const { data } = await recaptcha({ + variables: { + recaptchaToken, + }, + }); + + return data.recaptcha; + } catch (error: any) { + /* istanbul ignore next */ + toast.error(t('captchaError')); + } }; - return ( -
- -
- - Talawa Branding - { + const signupLink = async (e: ChangeEvent): Promise => { + e.preventDefault(); + + const { signfirstName, signlastName, signEmail, signPassword, cPassword } = + signformState; + + const recaptchaToken = recaptchaRef.current?.getValue(); + recaptchaRef.current?.reset(); + + const isVerified = await verifyRecaptcha(recaptchaToken); + /* istanbul ignore next */ + if (!isVerified) { + toast.error(t('Please_check_the_captcha')); + return; + } + + if ( + signfirstName.length > 1 && + signlastName.length > 1 && + signEmail.length >= 8 && + signPassword.length > 1 + ) { + if (cPassword == signPassword) { + try { + const { data: signUpData } = await signup({ + variables: { + firstName: signfirstName, + lastName: signlastName, + email: signEmail, + password: signPassword, + }, + }); + + /* istanbul ignore next */ + if (signUpData) { + toast.success(t('afterRegister')); + + setShowTab('LOGIN'); + + setSignFormState({ + signfirstName: '', + signlastName: '', + signEmail: '', + signPassword: '', + cPassword: '', + }); + } + } catch (error: any) { /* istanbul ignore next */ - currentMode === 'login' ? ( - - ) : ( - - ) + errorHandler(t, error); } -
-
+ } else { + toast.warn(t('passwordMismatches')); + } + } else { + toast.warn(t('fillCorrectly')); + } + }; + + const loginLink = async (e: ChangeEvent): Promise => { + e.preventDefault(); + + const recaptchaToken = recaptchaRef.current?.getValue(); + recaptchaRef.current?.reset(); + + const isVerified = await verifyRecaptcha(recaptchaToken); + /* istanbul ignore next */ + if (!isVerified) { + toast.error(t('Please_check_the_captcha')); + return; + } + + try { + const { data: loginData } = await login({ + variables: { + email: formState.email, + password: formState.password, + }, + }); + + /* istanbul ignore next */ + if (loginData) { + localStorage.setItem('token', loginData.login.accessToken); + localStorage.setItem('userId', loginData.login.user._id); + localStorage.setItem('refreshToken', loginData.login.refreshToken); + localStorage.setItem('IsLoggedIn', 'TRUE'); + navigator.clipboard.writeText(''); + if (localStorage.getItem('IsLoggedIn') == 'TRUE') { + history.push('/user/organizations/'); + } + } else { + toast.warn(t('notAuthorised')); + } + } catch (error: any) { + /* istanbul ignore next */ + errorHandler(t, error); + } + }; + + if (componentLoader || loginLoading || signinLoading || recaptchaLoading) { + return ; + } + + return ( + <> +
+ + +
+ +

{t('fromPalisadoes')}

+
+ + + + +
+ + + {/* LOGIN FORM */} +
+
+

+ {t('userLogin')} +

+ {t('email')} +
+ { + setFormState({ + ...formState, + email: e.target.value, + }); + }} + autoComplete="username" + data-testid="loginEmail" + /> + +
+ {t('password')} +
+ { + setFormState({ + ...formState, + password: e.target.value, + }); + }} + autoComplete="current-password" + /> + +
+
+ + {t('forgotPassword')} + +
+ {REACT_APP_USE_RECAPTCHA === 'yes' ? ( +
+ +
+ ) : ( + /* istanbul ignore next */ + <> + )} + +
+
+ {t('OR')} +
+ +
+
+ {/* REGISTER FORM */} +
+
+

+ {t('register')} +

+ + +
+ {t('firstName')} + { + setSignFormState({ + ...signformState, + signfirstName: e.target.value, + }); + }} + /> +
+ + +
+ {t('lastName')} + { + setSignFormState({ + ...signformState, + signlastName: e.target.value, + }); + }} + /> +
+ +
+
+ {t('email')} +
+ { + setSignFormState({ + ...signformState, + signEmail: e.target.value.toLowerCase(), + }); + }} + /> + +
+
+ +
+ {t('password')} +
+ setIsInputFocused(true)} + onBlur={(): void => setIsInputFocused(false)} + required + value={signformState.signPassword} + onChange={(e): void => { + setSignFormState({ + ...signformState, + signPassword: e.target.value, + }); + }} + /> + +
+ {isInputFocused && + signformState.signPassword.length < 8 && ( +
+ {t('atleast_8_char_long')} +
+ )} + {!isInputFocused && + signformState.signPassword.length > 0 && + signformState.signPassword.length < 8 && ( +
+ {t('atleast_8_char_long')} +
+ )} +
+
+ {t('confirmPassword')} +
+ { + setSignFormState({ + ...signformState, + cPassword: e.target.value, + }); + }} + data-testid="cpassword" + autoComplete="new-password" + /> + +
+ {signformState.cPassword.length > 0 && + signformState.signPassword !== + signformState.cPassword && ( +
+ {t('Password_and_Confirm_password_mismatches.')} +
+ )} +
+ {REACT_APP_USE_RECAPTCHA === 'yes' ? ( +
+ +
+ ) : ( + /* istanbul ignore next */ + <> + )} + +
+
+ {t('OR')} +
+ +
+
+
+ +
+
+ ); } + +export default loginPage; From 0e7a6a8139656bd70e8ab6bff9e82013eb4492be Mon Sep 17 00:00:00 2001 From: Akhilender Bongirwar <112749383+akhilender-bongirwar@users.noreply.github.com> Date: Sat, 6 Jan 2024 19:10:53 +0530 Subject: [PATCH 4/5] Implemented search triggering when the search icon is clicked (#1341) * Implemented search triggering when the search icon is clicked - Added code to implement search functionality even when the search icon is clicked. - Enhanced search functionality by allowing search initiation through both pressing 'Enter' and clicking the search icon in the user portal, ensuring uniformity. - Expanded test coverage to include all modified lines. - Users can now utilize dual search methods by both pressing 'Enter' and clicking the search icon to obtain search results. Signed-off-by: Akhilender * fix: Removed the unused variables to fix test Signed-off-by: Akhilender * fix: Fixed the failing tests Signed-off-by: Akhilender * fix: Covered the missing lines in the codecov report Signed-off-by: Akhilender * fix: Removed the comments - Resolved the changes requested. Signed-off-by: Akhilender * fix: Covered the missing lines in requests.test.tsx Signed-off-by: Akhilender --------- Signed-off-by: Akhilender --- .../UsersTableItem/UserTableItem.test.tsx | 12 ++-- .../UsersTableItem/UsersTableItem.tsx | 71 ++++++++++++------- src/screens/BlockUser/BlockUser.test.tsx | 31 ++++++++ src/screens/BlockUser/BlockUser.tsx | 30 +++++--- src/screens/OrgList/OrgList.test.tsx | 23 +++++- src/screens/OrgList/OrgList.tsx | 36 +++++++--- src/screens/OrgPost/OrgPost.test.tsx | 2 + src/screens/OrgPost/OrgPost.tsx | 29 +++++--- src/screens/Requests/Requests.test.tsx | 12 ++++ src/screens/Requests/Requests.tsx | 40 +++++++---- src/screens/UserPortal/Chat/Chat.test.tsx | 26 ++++++- src/screens/UserPortal/Chat/Chat.tsx | 33 +++++---- src/screens/UserPortal/Events/Events.test.tsx | 53 +++++++++++++- src/screens/UserPortal/Events/Events.tsx | 33 +++++---- .../Organizations/Organizations.test.tsx | 8 ++- .../Organizations/Organizations.tsx | 34 +++++---- src/screens/UserPortal/People/People.test.tsx | 30 +++++++- src/screens/UserPortal/People/People.tsx | 34 +++++---- src/screens/Users/Users.test.tsx | 9 ++- src/screens/Users/Users.tsx | 39 ++++++---- 20 files changed, 444 insertions(+), 141 deletions(-) diff --git a/src/components/UsersTableItem/UserTableItem.test.tsx b/src/components/UsersTableItem/UserTableItem.test.tsx index 4d91fa88df..6767f73229 100644 --- a/src/components/UsersTableItem/UserTableItem.test.tsx +++ b/src/components/UsersTableItem/UserTableItem.test.tsx @@ -8,7 +8,6 @@ import i18nForTest from 'utils/i18nForTest'; import type { InterfaceQueryUserListItem } from 'utils/interfaces'; import { MOCKS } from './UserTableItemMocks'; import UsersTableItem from './UsersTableItem'; - const link = new StaticMockLink(MOCKS, true); async function wait(ms = 100): Promise { @@ -370,10 +369,11 @@ describe('Testing User Table Item', () => { expect(screen.getByTestId(`changeRoleInOrgdef`)).toHaveValue('USER?def'); // Search for Joined Organization 1 + const searchBtn = screen.getByTestId(`searchBtnJoinedOrgs`); fireEvent.keyUp(inputBox, { - key: 'Enter', target: { value: 'Joined Organization 1' }, }); + fireEvent.click(searchBtn); expect(screen.getByText(/Joined Organization 1/i)).toBeInTheDocument(); expect( screen.queryByText(/Joined Organization 2/i) @@ -390,7 +390,8 @@ describe('Testing User Table Item', () => { // Now clear the search box fireEvent.keyUp(inputBox, { key: 'Enter', target: { value: '' } }); - + fireEvent.keyUp(inputBox, { target: { value: '' } }); + fireEvent.click(searchBtn); // Click on Creator Link fireEvent.click(screen.getByTestId(`creatorabc`)); expect(toast.success).toBeCalledWith('Profile Page Coming Soon !'); @@ -547,10 +548,11 @@ describe('Testing User Table Item', () => { expect(toast.success).toBeCalledWith('Profile Page Coming Soon !'); // Search for Blocked Organization 1 + const searchBtn = screen.getByTestId(`searchBtnOrgsBlockedBy`); fireEvent.keyUp(inputBox, { - key: 'Enter', target: { value: 'Blocked Organization 1' }, }); + fireEvent.click(searchBtn); expect(screen.getByText(/Blocked Organization 1/i)).toBeInTheDocument(); expect( screen.queryByText(/Blocked Organization 2/i) @@ -567,6 +569,8 @@ describe('Testing User Table Item', () => { // Now clear the search box fireEvent.keyUp(inputBox, { key: 'Enter', target: { value: '' } }); + fireEvent.keyUp(inputBox, { target: { value: '' } }); + fireEvent.click(searchBtn); // Click on Organization Link fireEvent.click(screen.getByText(/Blocked Organization 1/i)); diff --git a/src/components/UsersTableItem/UsersTableItem.tsx b/src/components/UsersTableItem/UsersTableItem.tsx index 59b3bef616..842329e44b 100644 --- a/src/components/UsersTableItem/UsersTableItem.tsx +++ b/src/components/UsersTableItem/UsersTableItem.tsx @@ -127,34 +127,53 @@ const UsersTableItem = (props: Props): JSX.Element => { function handleCreator(): void { toast.success('Profile Page Coming Soon !'); } - function handleSearchJoinedOrgs(e: any): void { + const searchJoinedOrgs = (value: string): void => { + setSearchByNameJoinedOrgs(value); + if (value == '') { + setJoinedOrgs(user.joinedOrganizations); + } else { + const filteredOrgs = user.joinedOrganizations.filter((org) => + org.name.toLowerCase().includes(value.toLowerCase()) + ); + setJoinedOrgs(filteredOrgs); + } + }; + const searchOrgsBlockedBy = (value: string): void => { + setSearchByNameOrgsBlockedBy(value); + if (value == '') { + setOrgsBlockedBy(user.organizationsBlockedBy); + } else { + const filteredOrgs = user.organizationsBlockedBy.filter((org) => + org.name.toLowerCase().includes(value.toLowerCase()) + ); + setOrgsBlockedBy(filteredOrgs); + } + }; + const handleSearchJoinedOrgs = (e: any): void => { if (e.key === 'Enter') { const { value } = e.target; - setSearchByNameJoinedOrgs(value); - if (value == '') { - setJoinedOrgs(user.joinedOrganizations); - } else { - const filteredOrgs = user.joinedOrganizations.filter((org) => - org.name.toLowerCase().includes(value.toLowerCase()) - ); - setJoinedOrgs(filteredOrgs); - } + searchJoinedOrgs(value); } - } - function handleSearcgByOrgsBlockedBy(e: any): void { + }; + const handleSearcgByOrgsBlockedBy = (e: any): void => { if (e.key === 'Enter') { const { value } = e.target; - setSearchByNameOrgsBlockedBy(value); - if (value == '') { - setOrgsBlockedBy(user.organizationsBlockedBy); - } else { - const filteredOrgs = user.organizationsBlockedBy.filter((org) => - org.name.toLowerCase().includes(value.toLowerCase()) - ); - setOrgsBlockedBy(filteredOrgs); - } + searchOrgsBlockedBy(value); } - } + }; + const handleSearchButtonClickJoinedOrgs = (): void => { + const inputValue = + (document.getElementById('orgname-joined-orgs') as HTMLInputElement) + ?.value || ''; + searchJoinedOrgs(inputValue); + }; + + const handleSearchButtonClickOrgsBlockedBy = (): void => { + const inputValue = + (document.getElementById('orgname-blocked-by') as HTMLInputElement) + ?.value || ''; + searchOrgsBlockedBy(inputValue); + }; /* istanbul ignore next */ function onHideRemoveUserModal(): void { @@ -223,7 +242,7 @@ const UsersTableItem = (props: Props): JSX.Element => {
{ @@ -396,7 +417,7 @@ const UsersTableItem = (props: Props): JSX.Element => {
{ tabIndex={-1} variant="danger" className={`position-absolute z-10 bottom-0 end-0 h-100 d-flex justify-content-center align-items-center`} + onClick={handleSearchButtonClickOrgsBlockedBy} + data-testid="searchBtnOrgsBlockedBy" > diff --git a/src/screens/BlockUser/BlockUser.test.tsx b/src/screens/BlockUser/BlockUser.test.tsx index 778a5ffa4e..ede51a9777 100644 --- a/src/screens/BlockUser/BlockUser.test.tsx +++ b/src/screens/BlockUser/BlockUser.test.tsx @@ -18,6 +18,7 @@ import { ToastContainer } from 'react-toastify'; import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; + import BlockUser from './BlockUser'; let userQueryCalled = false; @@ -630,4 +631,34 @@ describe('Testing Block/Unblock user screen', () => { ).toBeInTheDocument(); expect(window.location).toBeAt('/blockuser/id=orgid'); }); + + test('Testing Search functionality', async () => { + window.location.assign('/blockuser/id=orgid'); + + render( + + + + + + + + + + + ); + await wait(); + const searchBar = screen.getByTestId(/searchByName/i); + const searchBtn = screen.getByTestId(/searchBtn/i); + expect(searchBar).toBeInTheDocument(); + userEvent.type(searchBar, 'Dummy{enter}'); + await wait(); + userEvent.clear(searchBar); + userEvent.type(searchBar, 'Dummy'); + userEvent.click(searchBtn); + await wait(); + userEvent.clear(searchBar); + userEvent.type(searchBar, ''); + userEvent.click(searchBtn); + }); }); diff --git a/src/screens/BlockUser/BlockUser.tsx b/src/screens/BlockUser/BlockUser.tsx index 644044feb4..4bccd808a8 100644 --- a/src/screens/BlockUser/BlockUser.tsx +++ b/src/screens/BlockUser/BlockUser.tsx @@ -117,18 +117,29 @@ const Requests = (): JSX.Element => { toast.error(memberError.message); } - const handleSearch = (e: any): void => { + const handleSearch = (value: string): void => { + setSearchByName(value); + memberRefetch({ + orgId: currentUrl, + firstName_contains: searchByFirstName ? value : '', + lastName_contains: searchByFirstName ? '' : value, + }); + }; + + const handleSearchByEnter = (e: any): void => { if (e.key === 'Enter') { const { value } = e.target; - setSearchByName(value); - memberRefetch({ - orgId: currentUrl, - firstName_contains: searchByFirstName ? value : '', - lastName_contains: searchByFirstName ? '' : value, - }); + handleSearch(value); } }; + const handleSearchByBtnClick = (): void => { + const inputValue = + (document.getElementById('searchBlockedUsers') as HTMLInputElement) + ?.value || ''; + handleSearch(inputValue); + }; + const headerTitles: string[] = [ '#', t('name'), @@ -145,6 +156,7 @@ const Requests = (): JSX.Element => {
{ data-testid="searchByName" autoComplete="off" required - onKeyUp={handleSearch} + onKeyUp={handleSearchByEnter} /> diff --git a/src/screens/OrgList/OrgList.test.tsx b/src/screens/OrgList/OrgList.test.tsx index 35acc85c45..3cf16869f8 100644 --- a/src/screens/OrgList/OrgList.test.tsx +++ b/src/screens/OrgList/OrgList.test.tsx @@ -54,7 +54,7 @@ describe('Organisations Page testing as SuperAdmin', () => { image: new File(['hello'], 'hello.png', { type: 'image/png' }), }; - test('Testing search functionality', async () => { + test('Testing search functionality by pressing enter', async () => { localStorage.setItem('id', '123'); render( @@ -72,7 +72,28 @@ describe('Organisations Page testing as SuperAdmin', () => { // Test that the search bar filters organizations by name const searchBar = screen.getByTestId(/searchByName/i); expect(searchBar).toBeInTheDocument(); + userEvent.type(searchBar, 'Dummy{enter}'); + }); + + test('Testing search functionality by Btn click', async () => { + localStorage.setItem('id', '123'); + render( + + + + + + + + + + ); + await wait(); + + const searchBar = screen.getByTestId('searchByName'); + const searchBtn = screen.getByTestId('searchBtn'); userEvent.type(searchBar, 'Dummy'); + fireEvent.click(searchBtn); }); test('Should render no organisation warning alert when there are no organization', async () => { diff --git a/src/screens/OrgList/OrgList.tsx b/src/screens/OrgList/OrgList.tsx index 72058e8d96..abfcae17c3 100644 --- a/src/screens/OrgList/OrgList.tsx +++ b/src/screens/OrgList/OrgList.tsx @@ -223,19 +223,31 @@ function orgList(): JSX.Element { }; /* istanbul ignore next */ - const handleSearchByName = (e: any): void => { + const handleSearch = (value: string): void => { + setSearchByName(value); + if (value === '') { + resetAllParams(); + return; + } + refetchOrgs({ + filter: value, + }); + }; + + const handleSearchByEnter = (e: any): void => { if (e.key === 'Enter') { const { value } = e.target; - setSearchByName(value); - if (value == '') { - resetAllParams(); - return; - } - refetchOrgs({ - filter: value, - }); + handleSearch(value); } }; + + const handleSearchByBtnClick = (): void => { + const inputElement = document.getElementById( + 'searchOrgname' + ) as HTMLInputElement; + const inputValue = inputElement?.value || ''; + handleSearch(inputValue); + }; /* istanbul ignore next */ const loadMoreOrganizations = (): void => { console.log('loadMoreOrganizations'); @@ -300,17 +312,19 @@ function orgList(): JSX.Element {
diff --git a/src/screens/OrgPost/OrgPost.test.tsx b/src/screens/OrgPost/OrgPost.test.tsx index 5c8672e122..fecaa77169 100644 --- a/src/screens/OrgPost/OrgPost.test.tsx +++ b/src/screens/OrgPost/OrgPost.test.tsx @@ -245,7 +245,9 @@ describe('Organisation Post Page', () => { }); } await debounceWait(); + const searchBtn = screen.getByTestId('searchBtn'); userEvent.type(screen.getByPlaceholderText(/Search By/i), 'postone{enter}'); + userEvent.click(searchBtn); await debounceWait(); const sortDropdown = screen.getByTestId('sort'); userEvent.click(sortDropdown); diff --git a/src/screens/OrgPost/OrgPost.tsx b/src/screens/OrgPost/OrgPost.tsx index f1f2e477fa..f4dc98eee8 100644 --- a/src/screens/OrgPost/OrgPost.tsx +++ b/src/screens/OrgPost/OrgPost.tsx @@ -138,19 +138,28 @@ function orgPost(): JSX.Element { if (orgPostListError) { window.location.assign('/orglist'); } + const handleSearch = (value: string): void => { + const filterData = { + id: currentUrl, + title_contains: showTitle ? value : undefined, + text_contains: !showTitle ? value : undefined, + }; + refetch(filterData); + }; - const handleSearch = (e: any): void => { + const handleSearchByEnter = (e: any): void => { if (e.key === 'Enter') { const { value } = e.target; - const filterData = { - id: currentUrl, - title_contains: showTitle ? value : null, - text_contains: !showTitle ? value : null, - }; - refetch(filterData); + handleSearch(value); } }; + const handleSearchByBtnClick = (): void => { + const inputValue = + (document.getElementById('searchPosts') as HTMLInputElement)?.value || ''; + handleSearch(inputValue); + }; + const handleSorting = (option: string): void => { setSortingOption(option); }; @@ -196,17 +205,19 @@ function orgPost(): JSX.Element {
diff --git a/src/screens/Requests/Requests.test.tsx b/src/screens/Requests/Requests.test.tsx index 8ede1a4cdf..415a0b1ad9 100644 --- a/src/screens/Requests/Requests.test.tsx +++ b/src/screens/Requests/Requests.test.tsx @@ -113,8 +113,20 @@ describe('Testing Request screen', () => { await wait(); const searchInput = screen.getByTestId('searchByName'); + const searchBtn = screen.getByTestId('searchBtn'); + userEvent.type(searchInput, ''); + userEvent.click(searchBtn); + await wait(); + userEvent.clear(searchInput); + userEvent.type(searchInput, 'l{enter}'); + await wait(); + await screen.findByTestId('searchAndNotFound'); + userEvent.clear(searchInput); + userEvent.type(searchInput, 'l'); + userEvent.click(searchBtn); + await wait(); await screen.findByTestId('searchAndNotFound'); }); diff --git a/src/screens/Requests/Requests.tsx b/src/screens/Requests/Requests.tsx index c991d5d05e..d5c52ef2a4 100644 --- a/src/screens/Requests/Requests.tsx +++ b/src/screens/Requests/Requests.tsx @@ -238,23 +238,34 @@ const Requests = (): JSX.Element => { } }; - /* istanbul ignore next */ - const handleSearchByName = async (e: any): Promise => { + const handleSearch = async (value: string): Promise => { + setSearchByName(value); + if (value === '') { + resetAndRefetch(); + return; + } + await refetchUsers({ + firstName_contains: value, + lastName_contains: '', + // Later on we can add several search and filter options + }); + }; + + const handleSearchByEnter = (e: any): void => { if (e.key === 'Enter') { const { value } = e.target; - setSearchByName(value); - if (value === '') { - resetAndRefetch(); - return; - } - await refetchUsers({ - firstName_contains: value, - lastName_contains: '', - // Later on we can add several search and filter options - }); + handleSearch(value); } }; + const handleSearchByBtnClick = (): void => { + const inputElement = document.getElementById( + 'searchRequests' + ) as HTMLInputElement; + const inputValue = inputElement?.value || ''; + handleSearch(inputValue); + }; + const headerTitles: string[] = [ '#', t('name'), @@ -307,16 +318,19 @@ const Requests = (): JSX.Element => { > diff --git a/src/screens/UserPortal/Chat/Chat.test.tsx b/src/screens/UserPortal/Chat/Chat.test.tsx index 6476e9ee40..a2f709d8a4 100644 --- a/src/screens/UserPortal/Chat/Chat.test.tsx +++ b/src/screens/UserPortal/Chat/Chat.test.tsx @@ -148,7 +148,7 @@ describe('Testing People Screen [User Portal]', () => { expect(screen.queryAllByText('Noble Mittal')).not.toBe([]); }); - test('Search functionality works as expected', async () => { + test('Search functionality works as expected by pressing enter key', async () => { render( @@ -163,7 +163,31 @@ describe('Testing People Screen [User Portal]', () => { await wait(); + userEvent.type(screen.getByTestId('searchInput'), 'j{enter}'); + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText('John Cena')).toBeInTheDocument(); + expect(screen.queryByText('Noble Mittal')).not.toBeInTheDocument(); + }); + + test('Search functionality works as expected by clicking search Btn', async () => { + render( + + + + + + + + + + ); + + await wait(); + const searchBtn = screen.getByTestId('searchBtn'); userEvent.type(screen.getByTestId('searchInput'), 'j'); + userEvent.click(searchBtn); await wait(); expect(getOrganizationIdSpy).toHaveBeenCalled(); diff --git a/src/screens/UserPortal/Chat/Chat.tsx b/src/screens/UserPortal/Chat/Chat.tsx index 1ae9d5764d..aef6b24026 100644 --- a/src/screens/UserPortal/Chat/Chat.tsx +++ b/src/screens/UserPortal/Chat/Chat.tsx @@ -57,17 +57,23 @@ export default function chat(): JSX.Element { }, }); - const handleSearch = ( - event: React.ChangeEvent - ): void => { - const newFilter = event.target.value; - setFilterName(newFilter); + const handleSearch = (value: string): void => { + setFilterName(value); - const filter = { - firstName_contains: newFilter, - }; - - contactRefetch(filter); + contactRefetch({ + firstName_contains: value, + }); + }; + const handleSearchByEnter = (e: any): void => { + if (e.key === 'Enter') { + const { value } = e.target; + handleSearch(value); + } + }; + const handleSearchByBtnClick = (): void => { + const value = + (document.getElementById('searchChats') as HTMLInputElement)?.value || ''; + handleSearch(value); }; React.useEffect(() => { @@ -92,14 +98,17 @@ export default function chat(): JSX.Element { diff --git a/src/screens/UserPortal/Events/Events.test.tsx b/src/screens/UserPortal/Events/Events.test.tsx index 0559d39de5..52e658a641 100644 --- a/src/screens/UserPortal/Events/Events.test.tsx +++ b/src/screens/UserPortal/Events/Events.test.tsx @@ -288,7 +288,7 @@ describe('Testing Events Screen [User Portal]', () => { expect(screen.queryByText(mockEventTitle)).toBeInTheDocument(); }); - test('Search works as expected when user types in search input', async () => { + test('Search works as expected when user types in search input and press enter key', async () => { const getOrganizationIdSpy = jest .spyOn(getOrganizationId, 'default') .mockImplementation(() => { @@ -311,7 +311,7 @@ describe('Testing Events Screen [User Portal]', () => { expect(getOrganizationIdSpy).toHaveBeenCalled(); - const randomSearchInput = 'test'; + const randomSearchInput = 'test{enter}'; userEvent.type(screen.getByTestId('searchInput'), randomSearchInput); await wait(); @@ -332,6 +332,55 @@ describe('Testing Events Screen [User Portal]', () => { expect(screen.queryByText(mockEventTitleAbsent)).not.toBeInTheDocument(); }); + test('Search works as expected when user types in search input and clicks search Btn', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + const searchInput = screen.getByTestId('searchInput'); + const searchBtn = screen.getByTestId('searchBtn'); + userEvent.type(searchInput, ''); + userEvent.click(searchBtn); + await wait(); + userEvent.clear(searchInput); + userEvent.type(searchInput, 'test'); + userEvent.click(searchBtn); + + await wait(); + + let mockEventTitle = ''; + if (MOCKS[0].result?.data.eventsByOrganizationConnection) { + mockEventTitle = + MOCKS[0].result?.data.eventsByOrganizationConnection[0].title; + } + + let mockEventTitleAbsent = ''; + if (MOCKS[0].result?.data.eventsByOrganizationConnection) { + mockEventTitleAbsent = + MOCKS[0].result?.data.eventsByOrganizationConnection[1].title; + } + + expect(screen.queryByText(mockEventTitle)).toBeInTheDocument(); + expect(screen.queryByText(mockEventTitleAbsent)).not.toBeInTheDocument(); + }); + test('Create event works as expected when event is not an all day event.', async () => { const getOrganizationIdSpy = jest .spyOn(getOrganizationId, 'default') diff --git a/src/screens/UserPortal/Events/Events.tsx b/src/screens/UserPortal/Events/Events.tsx index 47332c10b4..13bf916b6c 100644 --- a/src/screens/UserPortal/Events/Events.tsx +++ b/src/screens/UserPortal/Events/Events.tsx @@ -54,7 +54,6 @@ export default function events(): JSX.Element { const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(5); const [events, setEvents] = React.useState([]); - const [filterName, setFilterName] = React.useState(''); const [mode, setMode] = React.useState(0); const [showCreateEventModal, setShowCreateEventModal] = React.useState(false); const [eventTitle, setEventTitle] = React.useState(''); @@ -148,16 +147,23 @@ export default function events(): JSX.Element { setPage(0); }; - const handleSearch = ( - event: React.ChangeEvent - ): void => { - const newFilter = event.target.value; - setFilterName(newFilter); - const filter = { - title_contains: newFilter, - }; + const handleSearch = (value: string): void => { + refetch({ + title_contains: value, + }); setPage(0); - refetch(filter); + }; + const handleSearchByEnter = (e: any): void => { + if (e.key === 'Enter') { + const { value } = e.target; + handleSearch(value); + } + }; + const handleSearchByBtnClick = (): void => { + const value = + (document.getElementById('searchEvents') as HTMLInputElement)?.value || + ''; + handleSearch(value); }; const handleEventTitleChange = ( @@ -210,15 +216,18 @@ export default function events(): JSX.Element { > diff --git a/src/screens/UserPortal/Organizations/Organizations.test.tsx b/src/screens/UserPortal/Organizations/Organizations.test.tsx index 6d92831940..f206ccab8f 100644 --- a/src/screens/UserPortal/Organizations/Organizations.test.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.test.tsx @@ -165,12 +165,16 @@ describe('Testing Organizations Screen [User Portal]', () => { ); await wait(); - - userEvent.type(screen.getByTestId('searchInput'), '2'); + const searchBtn = screen.getByTestId('searchBtn'); + userEvent.type(screen.getByTestId('searchInput'), '2{enter}'); await wait(); expect(screen.queryByText('anyOrganization2')).toBeInTheDocument(); expect(screen.queryByText('anyOrganization1')).not.toBeInTheDocument(); + + userEvent.clear(screen.getByTestId('searchInput')); + userEvent.click(searchBtn); + await wait(); }); test('Mode is changed to joined organizations', async () => { diff --git a/src/screens/UserPortal/Organizations/Organizations.tsx b/src/screens/UserPortal/Organizations/Organizations.tsx index c0b77f805e..f2a75b023c 100644 --- a/src/screens/UserPortal/Organizations/Organizations.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.tsx @@ -74,17 +74,24 @@ export default function organizations(): JSX.Element { setPage(0); }; - const handleSearch = ( - event: React.ChangeEvent - ): void => { - const newFilter = event.target.value; - setFilterName(newFilter); - - const filter = { - filter: newFilter, - }; + const handleSearch = (value: string): void => { + setFilterName(value); - refetch(filter); + refetch({ + filter: value, + }); + }; + const handleSearchByEnter = (e: any): void => { + if (e.key === 'Enter') { + const { value } = e.target; + handleSearch(value); + } + }; + const handleSearchByBtnClick = (): void => { + const value = + (document.getElementById('searchUserOrgs') as HTMLInputElement)?.value || + ''; + handleSearch(value); }; /* istanbul ignore next */ @@ -124,14 +131,17 @@ export default function organizations(): JSX.Element { diff --git a/src/screens/UserPortal/People/People.test.tsx b/src/screens/UserPortal/People/People.test.tsx index b373a68e98..19af06123f 100644 --- a/src/screens/UserPortal/People/People.test.tsx +++ b/src/screens/UserPortal/People/People.test.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { act, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; - import { ORGANIZATIONS_MEMBER_CONNECTION_LIST, ORGANIZATION_ADMINS_LIST, @@ -156,7 +155,7 @@ describe('Testing People Screen [User Portal]', () => { expect(screen.queryAllByText('Noble Mittal')).not.toBe([]); }); - test('Search works properly', async () => { + test('Search works properly by pressing enter', async () => { render( @@ -171,7 +170,34 @@ describe('Testing People Screen [User Portal]', () => { await wait(); + userEvent.type(screen.getByTestId('searchInput'), 'j{enter}'); + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText('John Cena')).toBeInTheDocument(); + expect(screen.queryByText('Noble Mittal')).not.toBeInTheDocument(); + }); + + test('Search works properly by clicking search Btn', async () => { + render( + + + + + + + + + + ); + + await wait(); + const searchBtn = screen.getByTestId('searchBtn'); + userEvent.type(screen.getByTestId('searchInput'), ''); + userEvent.click(searchBtn); + await wait(); userEvent.type(screen.getByTestId('searchInput'), 'j'); + userEvent.click(searchBtn); await wait(); expect(getOrganizationIdSpy).toHaveBeenCalled(); diff --git a/src/screens/UserPortal/People/People.tsx b/src/screens/UserPortal/People/People.tsx index f8b4d7ca3d..5bf6991af5 100644 --- a/src/screens/UserPortal/People/People.tsx +++ b/src/screens/UserPortal/People/People.tsx @@ -31,7 +31,6 @@ export default function people(): JSX.Element { const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(5); const [members, setMembers] = React.useState([]); - const [filterName, setFilterName] = React.useState(''); const [mode, setMode] = React.useState(0); const organizationId = getOrganizationId(window.location.href); @@ -70,20 +69,26 @@ export default function people(): JSX.Element { setPage(0); }; - const handleSearch = ( - event: React.ChangeEvent - ): void => { - const newFilter = event.target.value; - setFilterName(newFilter); - - const filter = { + const handleSearch = (newFilter: string): void => { + refetch({ firstName_contains: newFilter, - }; + }); + }; - refetch(filter); + const handleSearchByEnter = (e: any): void => { + if (e.key === 'Enter') { + const { value } = e.target; + handleSearch(value); + } + }; + + const handleSearchByBtnClick = (): void => { + const inputValue = + (document.getElementById('searchPeople') as HTMLInputElement)?.value || + ''; + handleSearch(inputValue); }; - /* istanbul ignore next */ React.useEffect(() => { if (data) { setMembers(data.organizationsMemberConnection.edges); @@ -119,14 +124,17 @@ export default function people(): JSX.Element { diff --git a/src/screens/Users/Users.test.tsx b/src/screens/Users/Users.test.tsx index 7879d833fb..446fc57f7d 100644 --- a/src/screens/Users/Users.test.tsx +++ b/src/screens/Users/Users.test.tsx @@ -103,9 +103,11 @@ describe('Testing Users screen', () => { ); await wait(); - - const search1 = 'John{backspace}{backspace}{backspace}{backspace}'; + const searchBtn = screen.getByTestId('searchButton'); + const search1 = 'John'; userEvent.type(screen.getByTestId(/searchByName/i), search1); + userEvent.click(searchBtn); + await wait(); const search2 = 'Pete{backspace}{backspace}{backspace}{backspace}'; userEvent.type(screen.getByTestId(/searchByName/i), search2); @@ -119,7 +121,10 @@ describe('Testing Users screen', () => { const search5 = 'Xe'; userEvent.type(screen.getByTestId(/searchByName/i), search5); + userEvent.clear(screen.getByTestId(/searchByName/i)); userEvent.type(screen.getByTestId(/searchByName/i), ''); + userEvent.click(searchBtn); + await wait(); }); test('testing search not found', async () => { diff --git a/src/screens/Users/Users.tsx b/src/screens/Users/Users.tsx index 6ec5ff9192..86adefe967 100644 --- a/src/screens/Users/Users.tsx +++ b/src/screens/Users/Users.tsx @@ -108,22 +108,33 @@ const Users = (): JSX.Element => { } }, [loading]); - const handleSearchByName = (e: any): void => { - /* istanbul ignore next */ + const handleSearch = (value: string): void => { + setSearchByName(value); + if (value === '') { + resetAndRefetch(); + return; + } + refetchUsers({ + firstName_contains: value, + lastName_contains: '', + // Later on we can add several search and filter options + }); + }; + + const handleSearchByEnter = (e: any): void => { if (e.key === 'Enter') { const { value } = e.target; - setSearchByName(value); - if (value.length === 0) { - resetAndRefetch(); - return; - } - refetchUsers({ - firstName_contains: value, - lastName_contains: '', - // Later on we can add several search and filter options - }); + handleSearch(value); } }; + + const handleSearchByBtnClick = (): void => { + const inputElement = document.getElementById( + 'searchUsers' + ) as HTMLInputElement; + const inputValue = inputElement?.value || ''; + handleSearch(inputValue); + }; /* istanbul ignore next */ const resetAndRefetch = (): void => { refetchUsers({ @@ -243,17 +254,19 @@ const Users = (): JSX.Element => { > From 0367652598a84aab687f9c2da8a1b532e43c318f Mon Sep 17 00:00:00 2001 From: "Ta.run" <97682967+Tarunmeena0901@users.noreply.github.com> Date: Sat, 6 Jan 2024 19:16:20 +0530 Subject: [PATCH 5/5] feature: new github action which limit the file changes in single PR to 20 (#1316) * feature: integrated a github action which limit the number of file change to 20 in a pull request * applied python coding and documentation standards * typo fixed * typo fix in pull-request.yml * made the suggested changes * made suggested changes * removed unnecessary imports * added more arguments in pareser.add_arguments * made the suggested changes --- .github/workflows/count_changed_files.py | 132 +++++++++++++++++++++++ .github/workflows/pull-requests.yml | 16 +++ 2 files changed, 148 insertions(+) create mode 100644 .github/workflows/count_changed_files.py diff --git a/.github/workflows/count_changed_files.py b/.github/workflows/count_changed_files.py new file mode 100644 index 0000000000..64fbc8bbfa --- /dev/null +++ b/.github/workflows/count_changed_files.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +"""Script to limit number of file changes in single PR. + +Methodology: + + Analyses the Pull request to find if the count of file changed in a pr + exceeds a pre-defined nummber 20 + + This scripts encourages contributors to align with project practices, + reducing the likelihood of unintentional merges into incorrect branches. + +NOTE: + + This script complies with our python3 coding and documentation standards. + It complies with: + + 1) Pylint + 2) Pydocstyle + 3) Pycodestyle + 4) Flake8 + +""" + +import sys +import argparse +import subprocess + + +def _count_changed_files(base_branch, pr_branch): + """ + Count the number of changed files between two branches. + + Args: + base_branch (str): The base branch. + pr_branch (str): The PR branch. + + Returns: + int: The number of changed files. + + Raises: + SystemExit: If an error occurs during execution. + """ + base_branch = f"origin/{base_branch}" + pr_branch = f"origin/{pr_branch}" + + command = f"git diff --name-only {base_branch}...{pr_branch} | wc -l" + + try: + # Run git command to get the list of changed files + process = subprocess.Popen( + command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + output, error = process.communicate() + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + file_count = int(output.strip()) + return file_count + +def _arg_parser_resolver(): + """Resolve the CLI arguments provided by the user. + + Args: + None + + Returns: + result: Parsed argument object + + """ + parser = argparse.ArgumentParser() + parser.add_argument( + "--base_branch", + type=str, + required=True, + help="Base branch where pull request should be made." + ), + parser.add_argument( + "--pr_branch", + type=str, + required=True, + help="PR branch from where the pull request is made.", + ), + parser.add_argument( + "--file_count", + type=int, + default=20, + help="Number of files changes allowed in a single commit") + 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 base branch and PR commit from + command line arguments. + 2. Counts the number of changed files between the specified branches. + 3. Checks if the count of changed files exceeds the acceptable + limit (20). + 4. Provides informative messages based on the analysis. + + Raises: + SystemExit: If an error occurs during execution. + """ + + args = _arg_parser_resolver() + + base_branch = args.base_branch + pr_branch = args.pr_branch + + print(f"You are trying to merge on branch: {base_branch}") + print(f"You are making commit from your branch: {pr_branch}") + + # Count changed files + file_count = _count_changed_files(base_branch, pr_branch) + print(f"Number of changed files: {file_count}") + + # Check if the count exceeds 20 + if file_count > args.file_count: + print("Error: Too many files (greater than 20) changed in the pull request.") + print("Possible issues:") + print("- Contributor may be merging into an incorrect branch.") + print("- Source branch may be incorrect please use develop as source branch.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 4f58b904f9..41e3b90886 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -57,6 +57,22 @@ jobs: python .github/workflows/compare_translations.py --directory public/locales + Check-Changed-Files: + runs-on: ubuntu-latest + needs: Code-Quality-Checks + steps: + - name: Checkout code + uses: actions/checkout@v1 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Run Python script + run: | + python .github/workflows/count_changed_files.py --base_branch "${{ github.base_ref }}" --pr_branch "${{ github.head_ref }}" + Test-Application: name: Test Application runs-on: ubuntu-latest