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 diff --git a/public/locales/en.json b/public/locales/en.json index 29271d451a..7567c669ca 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -29,6 +29,36 @@ "admin": "ADMIN", "user": "USER" }, + "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", @@ -432,7 +462,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 3319862c65..0430899e72 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -29,6 +29,36 @@ "admin": "ADMIN", "user": "UTILISATEUR" }, + "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", @@ -429,7 +459,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 78284ac414..af82fe392a 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -29,6 +29,36 @@ "admin": "व्यवस्थापक", "user": "उपयोगकर्ता" }, + "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": "सभी देखें", @@ -428,7 +458,8 @@ "deleteMsg": "क्या आप इस संगठन को हटाना चाहते हैं?", "cancel": "रद्द करना", "confirmDelete": "हटाने की पुष्टि करें", - "longDelOrgMsg": "संगठन को हमेशा के लिए हटा देने के लिए संगठन हटाने के बटन पर क्लिक करके, उसके इवेंट्स, टैग्स और सभी संबंधित डेटा सहित सभी जानकारी हटा दी जाएगी।" + "longDelOrgMsg": "संगठन को हमेशा के लिए हटा देने के लिए संगठन हटाने के बटन पर क्लिक करके, उसके इवेंट्स, टैग्स और सभी संबंधित डेटा सहित सभी जानकारी हटा दी जाएगी।", + "successfullyDeletedSampleOrganization": "नमूना संगठन सफलतापूर्वक हटा दिया गया" }, "userUpdate": { "firstName": "पहला नाम", diff --git a/public/locales/sp.json b/public/locales/sp.json index 8c4e715797..301f8ba637 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -29,6 +29,36 @@ "admin": "ADMINISTRACIÓN", "user": "USUARIO" }, + "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", @@ -428,7 +458,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 2a6a2e0dc3..0ad5dfb189 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -29,6 +29,36 @@ "admin": "行政", "user": "用戶" }, + "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": "查看全部", @@ -428,7 +458,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')} 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/LoginPage/LoginPage.tsx b/src/screens/LoginPage/LoginPage.tsx index 8aa046258f..3376763097 100644 --- a/src/screens/LoginPage/LoginPage.tsx +++ b/src/screens/LoginPage/LoginPage.tsx @@ -234,8 +234,14 @@ function loginPage(): JSX.Element {
- -

{t('fromPalisadoes')}

+ + +

{t('fromPalisadoes')}

+
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/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/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 => { >