From 0254282030799e8f2e808c576737c763299a9ace Mon Sep 17 00:00:00 2001 From: Pranav Nathe <93403830+pranavnathe@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:59:53 +0530 Subject: [PATCH 1/9] #2894 Refactor CSS in CreateGroupChat.tsx (#3494) * Refactor CSS in CreateGroupChat.tsx * removed ignore comment --- .../CreateGroupChat/functions/default.md | 2 +- .../CreateGroupChat.module.css | 38 ------------------- .../CreateGroupChat/CreateGroupChat.tsx | 35 +++++++++++------ src/style/app.module.css | 2 + 4 files changed, 26 insertions(+), 51 deletions(-) delete mode 100644 src/components/UserPortal/CreateGroupChat/CreateGroupChat.module.css diff --git a/docs/docs/auto-docs/components/UserPortal/CreateGroupChat/CreateGroupChat/functions/default.md b/docs/docs/auto-docs/components/UserPortal/CreateGroupChat/CreateGroupChat/functions/default.md index ed94b1b811..11040b9d01 100644 --- a/docs/docs/auto-docs/components/UserPortal/CreateGroupChat/CreateGroupChat/functions/default.md +++ b/docs/docs/auto-docs/components/UserPortal/CreateGroupChat/CreateGroupChat/functions/default.md @@ -6,7 +6,7 @@ > **default**(`__namedParameters`): `JSX.Element` -Defined in: [src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx:63](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx#L63) +Defined in: [src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx:74](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx#L74) ## Parameters diff --git a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.module.css b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.module.css deleted file mode 100644 index 77fcfaf38f..0000000000 --- a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.module.css +++ /dev/null @@ -1,38 +0,0 @@ -.userData { - height: 400px; - overflow-y: scroll; - overflow-x: hidden !important; -} - -.modalContent { - width: 530px; -} - -.groupInfo { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.groupImage { - margin-bottom: 10px; -} - -.editImgBtn { - padding: 2px 6px 6px 8px; - border-radius: 100%; - background-color: white; - border: 1px solid #959595; - color: #959595; - outline: none; - position: relative; - top: -40px; - left: 40px; -} - -.chatImage { - height: 120px; - border-radius: 100%; - width: 120px; -} diff --git a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx index 9a3e603111..793f49741d 100644 --- a/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx +++ b/src/components/UserPortal/CreateGroupChat/CreateGroupChat.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { Paper, TableBody } from '@mui/material'; import { Button, Form, Modal } from 'react-bootstrap'; -import styles from './CreateGroupChat.module.css'; +import styles from '../../../style/app.module.css'; import type { ApolloQueryResult } from '@apollo/client'; import { useMutation, useQuery } from '@apollo/client'; import useLocalStorage from 'utils/useLocalstorage'; @@ -34,17 +34,28 @@ interface InterfaceCreateGroupChatProps { ) => Promise>; } +/** + * Styled table container with custom styles. + */ + +const StyledTableContainer = styled(TableContainer)<{ + component?: React.ElementType; +}>(() => ({ + borderRadius: 'var(--table-head-radius)', +})); + /** * Styled table cell with custom styles. */ -const StyledTableCell = styled(TableCell)(({ theme }) => ({ +const StyledTableCell = styled(TableCell)(() => ({ [`&.${tableCellClasses.head}`]: { - backgroundColor: ['#31bb6b', '!important'], - color: theme.palette.common.white, + backgroundColor: 'var(--table-head-bg)', + color: 'var(--table-header-color)', + fontSize: 'var(--font-size-header)', }, [`&.${tableCellClasses.body}`]: { - fontSize: 14, + fontSize: 'var(--font-size-table-body)', }, })); @@ -54,7 +65,7 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableRow = styled(TableRow)(() => ({ '&:last-child td, &:last-child th': { - border: 0, + border: 'var(--table-row-border)', }, })); @@ -82,7 +93,7 @@ export default function CreateGroupChat({ setAddUserModalisOpen(true); } - const toggleAddUserModal = /* istanbul ignore next */ (): void => + const toggleAddUserModal = (): void => setAddUserModalisOpen(!addUserModalisOpen); const { orgId: currentOrg } = useParams(); @@ -127,7 +138,6 @@ export default function CreateGroupChat({ const handleUserModalSearchChange = (e: React.FormEvent): void => { e.preventDefault(); - /* istanbul ignore next */ const [firstName, lastName] = userName.split(' '); const newFilterData = { @@ -256,7 +266,7 @@ export default function CreateGroupChat({ data-testid="searchUser" placeholder="searchFullName" autoComplete="off" - className={styles.inputFieldModal} + className={styles.inputField} value={userName} onChange={(e): void => { const { value } = e.target; @@ -266,14 +276,14 @@ export default function CreateGroupChat({ - + @@ -320,6 +330,7 @@ export default function CreateGroupChat({ ) : (
-
+ )} ); } -export default orgListCard; +export default OrgListCard; diff --git a/src/components/ProfileDropdown/ProfileDropdown.spec.tsx b/src/components/ProfileDropdown/ProfileDropdown.spec.tsx index 0fb3300d0d..06e3b4331a 100644 --- a/src/components/ProfileDropdown/ProfileDropdown.spec.tsx +++ b/src/components/ProfileDropdown/ProfileDropdown.spec.tsx @@ -94,13 +94,12 @@ describe('ProfileDropdown Component', () => { expect(screen.getByTestId('display-name')).toBeInTheDocument(); expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.getByText('User')).toBeInTheDocument(); expect(screen.getByTestId('display-type')).toBeInTheDocument(); expect(screen.getByAltText('profile picture')).toBeInTheDocument(); }); test('renders Super admin', () => { - setItem('SuperAdmin', true); + setItem('role', 'API Administrator'); render( @@ -108,10 +107,10 @@ describe('ProfileDropdown Component', () => { , ); - expect(screen.getByText('SuperAdmin')).toBeInTheDocument(); + expect(screen.getByText('API Administrator')).toBeInTheDocument(); }); test('renders Admin', () => { - setItem('AdminFor', ['123']); + setItem('role', 'administrator'); render( @@ -119,7 +118,7 @@ describe('ProfileDropdown Component', () => { , ); - expect(screen.getByText('Admin')).toBeInTheDocument(); + expect(screen.getByText('administrator')).toBeInTheDocument(); }); test('logout functionality clears local storage and redirects to home', async () => { @@ -142,8 +141,7 @@ describe('ProfileDropdown Component', () => { describe('Member screen routing testing', () => { test('member screen', async () => { - setItem('SuperAdmin', false); - setItem('AdminFor', []); + setItem('role', 'regular'); render( @@ -168,8 +166,7 @@ describe('ProfileDropdown Component', () => { }); test('navigates to /user/settings for a user', async () => { - setItem('SuperAdmin', false); - setItem('AdminFor', []); + setItem('role', 'regular'); render( diff --git a/src/components/ProfileDropdown/ProfileDropdown.tsx b/src/components/ProfileDropdown/ProfileDropdown.tsx index bd8535c038..27be407d26 100644 --- a/src/components/ProfileDropdown/ProfileDropdown.tsx +++ b/src/components/ProfileDropdown/ProfileDropdown.tsx @@ -26,13 +26,7 @@ const profileDropdown = (): JSX.Element => { const { t: tCommon } = useTranslation('common'); const [revokeRefreshToken] = useMutation(REVOKE_REFRESH_TOKEN); const { getItem } = useLocalStorage(); - const superAdmin = getItem('SuperAdmin'); - const adminFor = getItem('AdminFor'); - const userRole = superAdmin - ? 'SuperAdmin' - : adminFor?.length > 0 - ? 'Admin' - : 'User'; + const userRole = getItem('role'); const name = getItem('name') || ''; const userImage = getItem('UserImage'); const navigate = useNavigate(); @@ -95,7 +89,7 @@ const profileDropdown = (): JSX.Element => { - userRole === 'User' + userRole === 'regular' ? navigate(`/user/settings`) : navigate(`/member/${orgId || ''}`) } diff --git a/src/components/SecuredRoute/SecuredRoute.tsx b/src/components/SecuredRoute/SecuredRoute.tsx index 1119800215..ff1cde09df 100644 --- a/src/components/SecuredRoute/SecuredRoute.tsx +++ b/src/components/SecuredRoute/SecuredRoute.tsx @@ -15,10 +15,10 @@ const { getItem, setItem } = useLocalStorage(); */ const SecuredRoute = (): JSX.Element => { const isLoggedIn = getItem('IsLoggedIn'); - const adminFor = getItem('AdminFor'); + const role = getItem('role'); return isLoggedIn === 'TRUE' ? ( - <>{adminFor != null ? : } + <>{role === 'administrator' ? : } ) : ( ); diff --git a/src/components/SecuredRoute/securedRoute.spec.tsx b/src/components/SecuredRoute/securedRoute.spec.tsx new file mode 100644 index 0000000000..8031e9d443 --- /dev/null +++ b/src/components/SecuredRoute/securedRoute.spec.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import SecuredRoute from './SecuredRoute'; +import useLocalStorage from 'utils/useLocalstorage'; + +describe('SecuredRoute', () => { + it('for administrator', () => { + // Set the 'IsLoggedIn' value to 'TRUE' in localStorage to simulate a logged-in user and role administrator to simulate admin login. + const { setItem } = useLocalStorage(); + setItem('IsLoggedIn', 'TRUE'); + setItem('role', 'administrator'); + + render( + + + }> + + + + , + ); + }); + + it('for regular user', () => { + // Set the 'IsLoggedIn' value to 'TRUE' in localStorage to simulate a logged-in user and role regular to simulare a non admin user. + const { setItem } = useLocalStorage(); + setItem('IsLoggedIn', 'TRUE'); + setItem('role', 'regular'); + + render( + + + }> + + + + , + ); + + expect(screen.getByText('talawaUser')).toBeInTheDocument(); + }); +}); diff --git a/src/screens/LoginPage/LoginPage.spec.tsx b/src/screens/LoginPage/LoginPage.spec.tsx index 4c04785f1a..e8a3c9dd19 100644 --- a/src/screens/LoginPage/LoginPage.spec.tsx +++ b/src/screens/LoginPage/LoginPage.spec.tsx @@ -42,6 +42,7 @@ const MOCKS = [ signIn: { user: { id: '1', + role: 'administrator', }, authenticationToken: 'authenticationToken', }, @@ -52,20 +53,16 @@ const MOCKS = [ request: { query: SIGNUP_MUTATION, variables: { - name: 'John Patrick', + name: 'John Doe', email: 'johndoe@gmail.com', - password: 'johnDoe', + password: 'Johndoe@123', }, }, result: { data: { - register: { + signUp: { user: { id: '1', - name: 'John Patrick', - emailAddress: 'johndoe@gmail.com', - role: 'User', - countryCode: '12', }, authenticationToken: 'authenticationToken', }, @@ -200,9 +197,24 @@ const MOCKS3 = [ }, ]; +const MOCKS4 = [ + { + request: { + query: SIGNIN_QUERY, + variables: { + email: 'johndoe@gmail.com', + password: 'johndoe1', + id: 'yttyt', + }, + }, + error: new Error('Invalid credentials'), + }, +]; + const link = new StaticMockLink(MOCKS, true); // const link2 = new StaticMockLink(MOCKS2, true); const link3 = new StaticMockLink(MOCKS3, true); +const link4 = new StaticMockLink(MOCKS4, true); async function wait(ms = 100): Promise { await act(() => { @@ -539,6 +551,50 @@ describe('Testing Login Page Screen', () => { expect(screen.getByTestId('goToRegisterPortion')).toBeInTheDocument(); }); + it('switches to login tab on successful registration correct data', async () => { + const formData = { + name: 'John Doe', + email: 'johndoe@gmail.com', + password: 'Johndoe@123', + confirmPassword: 'Johndoe@123', + orgId: 'abc', + }; + + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); + + await wait(); + userEvent.type(screen.getByPlaceholderText(/Name/i), formData.name); + + 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(); + }); + it('Testing toggle login register portion', async () => { render( @@ -592,6 +648,37 @@ describe('Testing Login Page Screen', () => { await wait(); }); + it('Testing wrong login functionality', async () => { + const formData = { + email: 'johndoe@gmail.com', + password: 'johndoe1', + }; + + 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(); + }); + it('Testing ReCaptcha functionality, it should refresh on unsuccessful SignUp, using duplicate email', async () => { const formData = { name: 'John Doe', @@ -1063,4 +1150,50 @@ describe('Talawa-API server fetch check', () => { expect(fetch).toHaveBeenCalledWith(BACKEND_URL); }); + + // it('Testing ReCaptcha functionality, it should fail', async () => { + // const formData = { + // name: 'John Doe', + // email: 'johndoe@gmail.com', + // password: 'johnDoe@1', + // confirmPassword: 'johnDoe@1', + // }; + + // vi.mock('Constant/constant.ts', async () => ({ + // ...(await vi.importActual('Constant/constant.ts')), + // REACT_APP_USE_RECAPTCHA: 'No', + // RECAPTCHA_SITE_KEY: 'xxx', + // })); + + // render( + // + // + // + // + // + // + // + // + // , + // ); + + // await wait(); + + // userEvent.click(screen.getByTestId(/goToRegisterPortion/i)); + + // userEvent.type(screen.getByPlaceholderText(/Name/i), formData.name); + + // 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 waitFor(() => { + // expect(resetReCAPTCHA).toBeCalled(); + // }); + // }); }); diff --git a/src/screens/LoginPage/LoginPage.tsx b/src/screens/LoginPage/LoginPage.tsx index 7b17fc2c57..ea34138f9f 100644 --- a/src/screens/LoginPage/LoginPage.tsx +++ b/src/screens/LoginPage/LoginPage.tsx @@ -186,8 +186,7 @@ const loginPage = (): JSX.Element => { const signupLink = async (e: ChangeEvent): Promise => { e.preventDefault(); - const { signName, signEmail, signPassword, cPassword, signOrg } = - signformState; + const { signName, signEmail, signPassword, cPassword } = signformState; const isVerified = await verifyRecaptcha(recaptchaToken); @@ -226,7 +225,6 @@ const loginPage = (): JSX.Element => { name: signName, email: signEmail, password: signPassword, - orgId: signOrg, }, }); @@ -282,7 +280,6 @@ const loginPage = (): JSX.Element => { password: formState.password, }, }); - console.log(signInData); if (signInData) { if (signInData.signIn.user.countryCode !== null) { @@ -292,7 +289,6 @@ const loginPage = (): JSX.Element => { const { signIn } = signInData; const { user, authenticationToken } = signIn; const isAdmin: boolean = user.role === 'administrator'; - if (role === 'admin' && !isAdmin) { toast.warn(tErrors('notAuthorised') as string); return; @@ -303,10 +299,11 @@ const loginPage = (): JSX.Element => { setItem('IsLoggedIn', 'TRUE'); setItem('name', user.name); setItem('email', user.emailAddress); + setItem('role', user.role); + setItem('UserImage', user.avatarURL || ''); // setItem('FirstName', user.firstName); // setItem('LastName', user.lastName); // setItem('UserImage', user.avatarURL); - if (role === 'admin') { setItem('id', loggedInUserId); } else { diff --git a/src/screens/OrgList/OrgList.spec.tsx b/src/screens/OrgList/OrgList.spec.tsx index 80a0bf1233..562f48ecd0 100644 --- a/src/screens/OrgList/OrgList.spec.tsx +++ b/src/screens/OrgList/OrgList.spec.tsx @@ -18,13 +18,7 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; import OrgList from './OrgList'; -import { - MOCKS, - MOCKS_ADMIN, - MOCKS_EMPTY, - MOCKS_WITH_ERROR, -} from './OrgListMocks'; -import { ToastContainer, toast } from 'react-toastify'; +import { MOCKS, MOCKS_ADMIN, MOCKS_EMPTY } from './OrgListMocks'; import useLocalStorage from 'utils/useLocalstorage'; import { vi } from 'vitest'; @@ -54,28 +48,51 @@ describe('Organisations Page testing as SuperAdmin', () => { setItem('id', '123'); const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_EMPTY, true); - const link3 = new StaticMockLink(MOCKS_WITH_ERROR, true); - - const formData = { - name: 'Dummy Organization', - description: 'This is a dummy organization', - address: { - city: 'Kingston', - countryCode: 'JM', - dependentLocality: 'Sample Dependent Locality', - line1: '123 Jamaica Street', - line2: 'Apartment 456', - postalCode: 'JM12345', - sortingCode: 'ABC-123', - state: 'Kingston Parish', - }, - image: new File(['hello'], 'hello.png', { type: 'image/png' }), - }; - test('Should display organisations for superAdmin even if admin For field is empty', async () => { - window.location.assign('/'); + // const link3 = new StaticMockLink(MOCKS_WITH_ERROR, true); + + // const formData = { + // name: 'Dummy Organization', + // description: 'This is a dummy organization', + // address: { + // city: 'Kingston', + // countryCode: 'JM', + // dependentLocality: 'Sample Dependent Locality', + // line1: '123 Jamaica Street', + // line2: 'Apartment 456', + // postalCode: 'JM12345', + // sortingCode: 'ABC-123', + // state: 'Kingston Parish', + // }, + // image: new File(['hello'], 'hello.png', { type: 'image/png' }), + // }; + // test('Should display organisations for superAdmin even if admin For field is empty', async () => { + // window.location.assign('/'); + // setItem('id', '123'); + // setItem('SuperAdmin', true); + // setItem('AdminFor', []); + + // render( + // + // + // + // + // + // + // + // + // , + // ); + + // await wait(); + // expect( + // screen.queryByText('Organizations Not Found'), + // ).not.toBeInTheDocument(); + // }); + + test('Testing search functionality by pressing enter', async () => { setItem('id', '123'); setItem('SuperAdmin', true); - setItem('AdminFor', []); + setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); render( @@ -88,18 +105,18 @@ describe('Organisations Page testing as SuperAdmin', () => { , ); - await wait(); - expect( - screen.queryByText('Organizations Not Found'), - ).not.toBeInTheDocument(); + + // 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 pressing enter', async () => { + test('Testing search functionality by Btn click', async () => { setItem('id', '123'); setItem('SuperAdmin', true); setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); - render( @@ -113,16 +130,14 @@ describe('Organisations Page testing as SuperAdmin', () => { ); await wait(); - // Test that the search bar filters organizations by name - const searchBar = screen.getByTestId(/searchByName/i); - expect(searchBar).toBeInTheDocument(); - userEvent.type(searchBar, 'Dummy{enter}'); + const searchBar = screen.getByTestId('searchByName'); + const searchBtn = screen.getByTestId('searchBtn'); + userEvent.type(searchBar, 'Dummy'); + fireEvent.click(searchBtn); }); - test('Testing search functionality by Btn click', async () => { + test('Testing search functionality by with empty search bar', async () => { setItem('id', '123'); - setItem('SuperAdmin', true); - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); render( @@ -138,15 +153,14 @@ describe('Organisations Page testing as SuperAdmin', () => { const searchBar = screen.getByTestId('searchByName'); const searchBtn = screen.getByTestId('searchBtn'); - userEvent.type(searchBar, 'Dummy'); + userEvent.type(searchBar, ''); fireEvent.click(searchBtn); }); test('Should render no organisation warning alert when there are no organization', async () => { window.location.assign('/'); setItem('id', '123'); - setItem('SuperAdmin', true); - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + setItem('role', 'administrator'); render( @@ -169,8 +183,7 @@ describe('Organisations Page testing as SuperAdmin', () => { test('Testing Organization data is not present', async () => { setItem('id', '123'); - setItem('SuperAdmin', false); - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + setItem('role', 'administrator'); render( @@ -185,44 +198,14 @@ describe('Organisations Page testing as SuperAdmin', () => { await wait(); }); - test('Testing create organization modal', async () => { + test('testing scroll', async () => { setItem('id', '123'); - setItem('SuperAdmin', true); - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); - - render( - - - - - - - - - , - ); - - screen.debug(); - - expect(localStorage.setItem).toHaveBeenLastCalledWith( - 'Talawa-admin_AdminFor', - JSON.stringify([{ name: 'adi', _id: '1234', image: '' }]), - ); - - expect(screen.getByTestId(/createOrganizationBtn/i)).toBeInTheDocument(); - }); - - test('Create organization model should work properly', async () => { - setItem('id', '123'); - setItem('SuperAdmin', true); - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); render( - @@ -230,249 +213,305 @@ describe('Organisations Page testing as SuperAdmin', () => { , ); - await wait(500); - - expect(localStorage.setItem).toHaveBeenLastCalledWith( - 'Talawa-admin_AdminFor', - JSON.stringify([{ name: 'adi', _id: '1234', image: '' }]), - ); - - userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); - - userEvent.type(screen.getByTestId(/modalOrganizationName/i), formData.name); - userEvent.type( - screen.getByPlaceholderText(/Description/i), - formData.description, - ); - userEvent.type(screen.getByPlaceholderText(/City/i), formData.address.city); - userEvent.type( - screen.getByPlaceholderText(/Postal Code/i), - formData.address.postalCode, - ); - userEvent.type( - screen.getByPlaceholderText(/State \/ Province/i), - formData.address.state, - ); - - userEvent.selectOptions( - screen.getByTestId('countrycode'), - formData.address.countryCode, - ); - userEvent.type( - screen.getByPlaceholderText(/Line 1/i), - formData.address.line1, - ); - userEvent.type( - screen.getByPlaceholderText(/Line 2/i), - formData.address.line2, - ); - userEvent.type( - screen.getByPlaceholderText(/Sorting Code/i), - formData.address.sortingCode, - ); - userEvent.type( - screen.getByPlaceholderText(/Dependent Locality/i), - formData.address.dependentLocality, - ); - userEvent.click(screen.getByTestId(/userRegistrationRequired/i)); - userEvent.click(screen.getByTestId(/visibleInSearch/i)); - - expect(screen.getByTestId(/modalOrganizationName/i)).toHaveValue( - formData.name, - ); - expect(screen.getByPlaceholderText(/Description/i)).toHaveValue( - formData.description, - ); - //Checking the fields for the address object in the formdata. - const { address } = formData; - expect(screen.getByPlaceholderText(/City/i)).toHaveValue(address.city); - expect(screen.getByPlaceholderText(/State \/ Province/i)).toHaveValue( - address.state, - ); - expect(screen.getByPlaceholderText(/Dependent Locality/i)).toHaveValue( - address.dependentLocality, - ); - expect(screen.getByPlaceholderText(/Line 1/i)).toHaveValue(address.line1); - expect(screen.getByPlaceholderText(/Line 2/i)).toHaveValue(address.line2); - expect(screen.getByPlaceholderText(/Postal Code/i)).toHaveValue( - address.postalCode, - ); - expect(screen.getByTestId(/countrycode/i)).toHaveValue(address.countryCode); - expect(screen.getByPlaceholderText(/Sorting Code/i)).toHaveValue( - address.sortingCode, - ); - expect(screen.getByTestId(/userRegistrationRequired/i)).not.toBeChecked(); - expect(screen.getByTestId(/visibleInSearch/i)).toBeChecked(); - expect(screen.getByLabelText(/Display Image/i)).toBeTruthy(); - const displayImage = screen.getByTestId('organisationImage'); - userEvent.upload(displayImage, formData.image); - userEvent.click(screen.getByTestId(/submitOrganizationForm/i)); await waitFor(() => { - expect( - screen.queryByText(/Congratulation the Organization is created/i), - ).toBeInTheDocument(); - }); - }); - - test('Plugin Notification model should work properly', async () => { - setItem('id', '123'); - setItem('SuperAdmin', true); - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); - - render( - - - - - - - - - - , - ); - - await wait(500); - - expect(localStorage.setItem).toHaveBeenLastCalledWith( - 'Talawa-admin_AdminFor', - JSON.stringify([{ name: 'adi', _id: '1234', image: '' }]), - ); - - userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); - - userEvent.type(screen.getByTestId(/modalOrganizationName/i), formData.name); - userEvent.type( - screen.getByPlaceholderText(/Description/i), - formData.description, - ); - userEvent.type(screen.getByPlaceholderText(/City/i), formData.address.city); - userEvent.type( - screen.getByPlaceholderText(/State \/ Province/i), - formData.address.state, - ); - userEvent.type( - screen.getByPlaceholderText(/Postal Code/i), - formData.address.postalCode, - ); - userEvent.selectOptions( - screen.getByTestId('countrycode'), - formData.address.countryCode, - ); - userEvent.type( - screen.getByPlaceholderText(/Line 1/i), - formData.address.line1, - ); - userEvent.type( - screen.getByPlaceholderText(/Line 2/i), - formData.address.line2, - ); - userEvent.type( - screen.getByPlaceholderText(/Sorting Code/i), - formData.address.sortingCode, - ); - userEvent.type( - screen.getByPlaceholderText(/Dependent Locality/i), - formData.address.dependentLocality, - ); - userEvent.click(screen.getByTestId(/userRegistrationRequired/i)); - userEvent.click(screen.getByTestId(/visibleInSearch/i)); - - expect(screen.getByTestId(/modalOrganizationName/i)).toHaveValue( - formData.name, - ); - expect(screen.getByPlaceholderText(/Description/i)).toHaveValue( - formData.description, - ); - //Checking the fields for the address object in the formdata. - const { address } = formData; - expect(screen.getByPlaceholderText(/City/i)).toHaveValue(address.city); - expect(screen.getByPlaceholderText(/State \/ Province/i)).toHaveValue( - address.state, - ); - expect(screen.getByPlaceholderText(/Dependent Locality/i)).toHaveValue( - address.dependentLocality, - ); - expect(screen.getByPlaceholderText(/Line 1/i)).toHaveValue(address.line1); - expect(screen.getByPlaceholderText(/Line 2/i)).toHaveValue(address.line2); - expect(screen.getByPlaceholderText(/Postal Code/i)).toHaveValue( - address.postalCode, - ); - expect(screen.getByTestId(/countrycode/i)).toHaveValue(address.countryCode); - expect(screen.getByPlaceholderText(/Sorting Code/i)).toHaveValue( - address.sortingCode, - ); - expect(screen.getByTestId(/userRegistrationRequired/i)).not.toBeChecked(); - expect(screen.getByTestId(/visibleInSearch/i)).toBeChecked(); - expect(screen.getByLabelText(/Display Image/i)).toBeTruthy(); - - userEvent.click(screen.getByTestId(/submitOrganizationForm/i)); - // await act(async () => { - // await new Promise((resolve) => setTimeout(resolve, 1000)); - // }); - await waitFor(() => - expect( - screen.queryByText(/Congratulation the Organization is created/i), - ).toBeInTheDocument(), - ); - await waitFor(() => { - screen.findByTestId(/pluginNotificationHeader/i); + expect(screen.queryByText(/Create Organization/i)).toBeNull(); }); - // userEvent.click(screen.getByTestId(/enableEverythingForm/i)); - userEvent.click(screen.getByTestId(/enableEverythingForm/i)); - }); - test('Testing create sample organization working properly', async () => { - setItem('id', '123'); - setItem('SuperAdmin', true); - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + // Wait for initial organizations to load + expect(await screen.findByText('Organization 1')).toBeInTheDocument(); + expect(await screen.findByText('Organization 2')).toBeInTheDocument(); - render( - - - - - - - - - - , - ); - await wait(); - userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); - userEvent.click(screen.getByTestId(/createSampleOrganizationBtn/i)); - await waitFor(() => - expect( - screen.queryByText(/Sample Organization Successfully created/i), - ).toBeInTheDocument(), - ); + fireEvent.scroll(window, { target: { scrollY: 1000 } }); }); - test('Testing error handling for CreateSampleOrg', async () => { - setItem('id', '123'); - setItem('SuperAdmin', true); - setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); - vi.spyOn(toast, 'error'); - render( - - - - - - - - , - ); - await wait(); - userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); - userEvent.click(screen.getByTestId(/createSampleOrganizationBtn/i)); - await waitFor(() => - expect( - screen.queryByText(/Only one sample organization allowed/i), - ).toBeInTheDocument(), - ); - }); + // test('Testing create organization modal', async () => { + // setItem('id', '123'); + // setItem('SuperAdmin', true); + // setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + + // render( + // + // + // + // + // + // + // + // + // , + // ); + + // screen.debug(); + + // expect(localStorage.setItem).toHaveBeenLastCalledWith( + // 'Talawa-admin_AdminFor', + // JSON.stringify([{ name: 'adi', _id: '1234', image: '' }]), + // ); + + // expect(screen.getByTestId(/createOrganizationBtn/i)).toBeInTheDocument(); + // }); + + // test('Create organization model should work properly', async () => { + // setItem('id', '123'); + // setItem('SuperAdmin', true); + // setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + + // render( + // + // + // + // + // + // + // + // + // + // , + // ); + + // await wait(500); + + // expect(localStorage.setItem).toHaveBeenLastCalledWith( + // 'Talawa-admin_AdminFor', + // JSON.stringify([{ name: 'adi', _id: '1234', image: '' }]), + // ); + + // userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); + + // userEvent.type(screen.getByTestId(/modalOrganizationName/i), formData.name); + // userEvent.type( + // screen.getByPlaceholderText(/Description/i), + // formData.description, + // ); + // userEvent.type(screen.getByPlaceholderText(/City/i), formData.address.city); + // userEvent.type( + // screen.getByPlaceholderText(/Postal Code/i), + // formData.address.postalCode, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/State \/ Province/i), + // formData.address.state, + // ); + + // userEvent.selectOptions( + // screen.getByTestId('countrycode'), + // formData.address.countryCode, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Line 1/i), + // formData.address.line1, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Line 2/i), + // formData.address.line2, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Sorting Code/i), + // formData.address.sortingCode, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Dependent Locality/i), + // formData.address.dependentLocality, + // ); + // userEvent.click(screen.getByTestId(/userRegistrationRequired/i)); + // userEvent.click(screen.getByTestId(/visibleInSearch/i)); + + // expect(screen.getByTestId(/modalOrganizationName/i)).toHaveValue( + // formData.name, + // ); + // expect(screen.getByPlaceholderText(/Description/i)).toHaveValue( + // formData.description, + // ); + // //Checking the fields for the address object in the formdata. + // const { address } = formData; + // expect(screen.getByPlaceholderText(/City/i)).toHaveValue(address.city); + // expect(screen.getByPlaceholderText(/State \/ Province/i)).toHaveValue( + // address.state, + // ); + // expect(screen.getByPlaceholderText(/Dependent Locality/i)).toHaveValue( + // address.dependentLocality, + // ); + // expect(screen.getByPlaceholderText(/Line 1/i)).toHaveValue(address.line1); + // expect(screen.getByPlaceholderText(/Line 2/i)).toHaveValue(address.line2); + // expect(screen.getByPlaceholderText(/Postal Code/i)).toHaveValue( + // address.postalCode, + // ); + // expect(screen.getByTestId(/countrycode/i)).toHaveValue(address.countryCode); + // expect(screen.getByPlaceholderText(/Sorting Code/i)).toHaveValue( + // address.sortingCode, + // ); + // expect(screen.getByTestId(/userRegistrationRequired/i)).not.toBeChecked(); + // expect(screen.getByTestId(/visibleInSearch/i)).toBeChecked(); + // expect(screen.getByLabelText(/Display Image/i)).toBeTruthy(); + // const displayImage = screen.getByTestId('organisationImage'); + // userEvent.upload(displayImage, formData.image); + // userEvent.click(screen.getByTestId(/submitOrganizationForm/i)); + // await waitFor(() => { + // expect( + // screen.queryByText(/Congratulation the Organization is created/i), + // ).toBeInTheDocument(); + // }); + // }); + + // test('Plugin Notification model should work properly', async () => { + // setItem('id', '123'); + // setItem('SuperAdmin', true); + // setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + + // render( + // + // + // + // + // + // + // + // + // + // , + // ); + + // await wait(500); + + // expect(localStorage.setItem).toHaveBeenLastCalledWith( + // 'Talawa-admin_AdminFor', + // JSON.stringify([{ name: 'adi', _id: '1234', image: '' }]), + // ); + + // userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); + + // userEvent.type(screen.getByTestId(/modalOrganizationName/i), formData.name); + // userEvent.type( + // screen.getByPlaceholderText(/Description/i), + // formData.description, + // ); + // userEvent.type(screen.getByPlaceholderText(/City/i), formData.address.city); + // userEvent.type( + // screen.getByPlaceholderText(/State \/ Province/i), + // formData.address.state, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Postal Code/i), + // formData.address.postalCode, + // ); + // userEvent.selectOptions( + // screen.getByTestId('countrycode'), + // formData.address.countryCode, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Line 1/i), + // formData.address.line1, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Line 2/i), + // formData.address.line2, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Sorting Code/i), + // formData.address.sortingCode, + // ); + // userEvent.type( + // screen.getByPlaceholderText(/Dependent Locality/i), + // formData.address.dependentLocality, + // ); + // userEvent.click(screen.getByTestId(/userRegistrationRequired/i)); + // userEvent.click(screen.getByTestId(/visibleInSearch/i)); + + // expect(screen.getByTestId(/modalOrganizationName/i)).toHaveValue( + // formData.name, + // ); + // expect(screen.getByPlaceholderText(/Description/i)).toHaveValue( + // formData.description, + // ); + // //Checking the fields for the address object in the formdata. + // const { address } = formData; + // expect(screen.getByPlaceholderText(/City/i)).toHaveValue(address.city); + // expect(screen.getByPlaceholderText(/State \/ Province/i)).toHaveValue( + // address.state, + // ); + // expect(screen.getByPlaceholderText(/Dependent Locality/i)).toHaveValue( + // address.dependentLocality, + // ); + // expect(screen.getByPlaceholderText(/Line 1/i)).toHaveValue(address.line1); + // expect(screen.getByPlaceholderText(/Line 2/i)).toHaveValue(address.line2); + // expect(screen.getByPlaceholderText(/Postal Code/i)).toHaveValue( + // address.postalCode, + // ); + // expect(screen.getByTestId(/countrycode/i)).toHaveValue(address.countryCode); + // expect(screen.getByPlaceholderText(/Sorting Code/i)).toHaveValue( + // address.sortingCode, + // ); + // expect(screen.getByTestId(/userRegistrationRequired/i)).not.toBeChecked(); + // expect(screen.getByTestId(/visibleInSearch/i)).toBeChecked(); + // expect(screen.getByLabelText(/Display Image/i)).toBeTruthy(); + + // userEvent.click(screen.getByTestId(/submitOrganizationForm/i)); + // // await act(async () => { + // // await new Promise((resolve) => setTimeout(resolve, 1000)); + // // }); + // await waitFor(() => + // expect( + // screen.queryByText(/Congratulation the Organization is created/i), + // ).toBeInTheDocument(), + // ); + // await waitFor(() => { + // screen.findByTestId(/pluginNotificationHeader/i); + // }); + // // userEvent.click(screen.getByTestId(/enableEverythingForm/i)); + // userEvent.click(screen.getByTestId(/enableEverythingForm/i)); + // }); + + // test('Testing create sample organization working properly', async () => { + // setItem('id', '123'); + // setItem('SuperAdmin', true); + // setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + + // render( + // + // + // + // + // + // + // + // + // + // , + // ); + // await wait(); + // userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); + // userEvent.click(screen.getByTestId(/createSampleOrganizationBtn/i)); + // await waitFor(() => + // expect( + // screen.queryByText(/Sample Organization Successfully created/i), + // ).toBeInTheDocument(), + // ); + // }); + // + // setItem('id', '123'); + // setItem('SuperAdmin', true); + // setItem('AdminFor', [{ name: 'adi', _id: '1234', image: '' }]); + + // vi.spyOn(toast, 'error'); + // render( + // + // + // + // + // + // + // + // , + // ); + // await wait(); + // userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); + // userEvent.click(screen.getByTestId(/createSampleOrganizationBtn/i)); + // await waitFor(() => + // expect( + // screen.queryByText(/Only one sample organization allowed/i), + // ).toBeInTheDocument(), + // ); + // }); }); describe('Organisations Page testing as Admin', () => { diff --git a/src/screens/OrgList/OrgList.tsx b/src/screens/OrgList/OrgList.tsx index 4098873195..20ee9fd49e 100644 --- a/src/screens/OrgList/OrgList.tsx +++ b/src/screens/OrgList/OrgList.tsx @@ -1,31 +1,24 @@ import React, { useEffect, useState } from 'react'; -import { useMutation, useQuery } from '@apollo/client'; +import { useQuery } from '@apollo/client'; +// import { +// CREATE_ORGANIZATION_MUTATION, +// CREATE_SAMPLE_ORGANIZATION_MUTATION, +// } from 'GraphQl/Mutations/mutations'; import { - CREATE_ORGANIZATION_MUTATION, - CREATE_SAMPLE_ORGANIZATION_MUTATION, -} from 'GraphQl/Mutations/mutations'; -import { - ORGANIZATION_CONNECTION_LIST, - USER_ORGANIZATION_LIST, + USER_JOINED_ORGANIZATIONS_PG, + CURRENT_USER, } from 'GraphQl/Queries/Queries'; import OrgListCard from 'components/OrgListCard/OrgListCard'; -import type { ChangeEvent } from 'react'; -import Button from 'react-bootstrap/Button'; -import Modal from 'react-bootstrap/Modal'; import { useTranslation } from 'react-i18next'; import InfiniteScroll from 'react-infinite-scroll-component'; -import { Link } from 'react-router-dom'; -import { toast } from 'react-toastify'; import { errorHandler } from 'utils/errorHandler'; import type { - InterfaceOrgConnectionInfoType, - InterfaceOrgConnectionType, - InterfaceUserType, + InterfaceOrgConnectionInfoTypePG, + InterfaceCurrentUserTypePG, } from 'utils/interfaces'; import useLocalStorage from 'utils/useLocalstorage'; import styles from '../../style/app.module.css'; -import OrganizationModal from './OrganizationModal'; import SortingButton from 'subComponents/SortingButton'; import SearchBar from 'subComponents/SearchBar'; @@ -57,24 +50,24 @@ import SearchBar from 'subComponents/SearchBar'; function orgList(): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'orgList' }); const { t: tCommon } = useTranslation('common'); - const [dialogModalisOpen, setdialogModalIsOpen] = useState(false); - const [dialogRedirectOrgId, setDialogRedirectOrgId] = useState(''); + // const [dialogModalisOpen, setdialogModalIsOpen] = useState(false); + // const [dialogRedirectOrgId, setDialogRedirectOrgId] = useState(''); - function openDialogModal(redirectOrgId: string): void { - setDialogRedirectOrgId(redirectOrgId); - setdialogModalIsOpen(true); - } + // function openDialogModal(redirectOrgId: string): void { + // setDialogRedirectOrgId(redirectOrgId); + // setdialogModalIsOpen(true); + // } const { getItem } = useLocalStorage(); const superAdmin = getItem('SuperAdmin'); const adminFor = getItem('AdminFor'); - function closeDialogModal(): void { - setdialogModalIsOpen(false); - } + // function closeDialogModal(): void { + // setdialogModalIsOpen(false); + // } - const toggleDialogModal = (): void => - setdialogModalIsOpen(!dialogModalisOpen); + // const toggleDialogModal = (): void => + // setdialogModalIsOpen(!dialogModalisOpen); document.title = t('title'); @@ -88,173 +81,189 @@ function orgList(): JSX.Element { const [hasMore, sethasMore] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); const [searchByName, setSearchByName] = useState(''); - const [showModal, setShowModal] = useState(false); - const [formState, setFormState] = useState({ - name: '', - descrip: '', - userRegistrationRequired: true, - visible: false, - address: { - city: '', - countryCode: '', - dependentLocality: '', - line1: '', - line2: '', - postalCode: '', - sortingCode: '', - state: '', - }, - image: '', - }); - - const toggleModal = (): void => setShowModal(!showModal); - const [create] = useMutation(CREATE_ORGANIZATION_MUTATION); - const [createSampleOrganization] = useMutation( - CREATE_SAMPLE_ORGANIZATION_MUTATION, - ); + // const [showModal, setShowModal] = useState(false); + // const [formState, setFormState] = useState({ + // name: '', + // descrip: '', + // userRegistrationRequired: true, + // visible: false, + // address: { + // city: '', + // countryCode: '', + // dependentLocality: '', + // line1: '', + // line2: '', + // postalCode: '', + // sortingCode: '', + // state: '', + // }, + // image: '', + // }); + + // const toggleModal = (): void => setShowModal(!showModal); + // const [create] = useMutation(CREATE_ORGANIZATION_MUTATION); + // const [createSampleOrganization] = useMutation( + // CREATE_SAMPLE_ORGANIZATION_MUTATION, + // ); const { data: userData, error: errorUser, }: { - data: InterfaceUserType | undefined; + data: InterfaceCurrentUserTypePG | undefined; loading: boolean; error?: Error | undefined; - } = useQuery(USER_ORGANIZATION_LIST, { + } = useQuery(CURRENT_USER, { variables: { userId: getItem('id') }, context: { headers: { authorization: `Bearer ${getItem('token')}` }, }, }); + // const { + // data: orgsData, + // loading, + // error: errorList, + // refetch: refetchOrgs, + // fetchMore, + // } = useQuery(ORGANIZATION_CONNECTION_LIST, { + // variables: { + // first: perPageResult, + // skip: 0, + // filter: searchByName, + // orderBy: + // sortingState.option === 'Latest' ? 'createdAt_DESC' : 'createdAt_ASC', + // }, + // notifyOnNetworkStatusChange: true, + // }); + const { - data: orgsData, + data: UsersOrgsData, loading, error: errorList, refetch: refetchOrgs, fetchMore, - } = useQuery(ORGANIZATION_CONNECTION_LIST, { + } = useQuery(USER_JOINED_ORGANIZATIONS_PG, { variables: { + id: getItem('id'), first: perPageResult, - skip: 0, - filter: searchByName, - orderBy: - sortingState.option === 'Latest' ? 'createdAt_DESC' : 'createdAt_ASC', }, notifyOnNetworkStatusChange: true, }); + const orgsData = UsersOrgsData?.user.organizationsWhereMember; + // To clear the search field and form fields on unmount - useEffect(() => { - return () => { - setSearchByName(''); - setFormState({ - name: '', - descrip: '', - userRegistrationRequired: true, - visible: false, - address: { - city: '', - countryCode: '', - dependentLocality: '', - line1: '', - line2: '', - postalCode: '', - sortingCode: '', - state: '', - }, - image: '', - }); - }; - }, []); + // useEffect(() => { + // return () => { + // setSearchByName(''); + // setFormState({ + // name: '', + // descrip: '', + // userRegistrationRequired: true, + // visible: false, + // address: { + // city: '', + // countryCode: '', + // dependentLocality: '', + // line1: '', + // line2: '', + // postalCode: '', + // sortingCode: '', + // state: '', + // }, + // image: '', + // }); + // }; + // }, []); useEffect(() => { setIsLoading(loading && isLoadingMore); }, [loading]); - const isAdminForCurrentOrg = ( - currentOrg: InterfaceOrgConnectionInfoType, - ): boolean => { - if (adminFor.length === 1) { - // If user is admin for one org only then check if that org is current org - return adminFor[0]._id === currentOrg._id; - } else { - // If user is admin for more than one org then check if current org is present in adminFor array - return ( - adminFor.some( - (org: { _id: string; name: string; image: string | null }) => - org._id === currentOrg._id, - ) ?? false - ); - } - }; - - const triggerCreateSampleOrg = (): void => { - createSampleOrganization() - .then(() => { - toast.success(t('sampleOrgSuccess') as string); - window.location.reload(); - }) - .catch(() => { - toast.error(t('sampleOrgDuplicate') as string); - }); - }; - - const createOrg = async (e: ChangeEvent): Promise => { - e.preventDefault(); - - const { - name: _name, - descrip: _descrip, - address: _address, - visible, - userRegistrationRequired, - image, - } = formState; - - const name = _name.trim(); - const descrip = _descrip.trim(); - const address = _address; - - try { - const { data } = await create({ - variables: { - name: name, - description: descrip, - address: address, - visibleInSearch: visible, - userRegistrationRequired: userRegistrationRequired, - image: image, - }, - }); - - if (data) { - toast.success('Congratulation the Organization is created'); - refetchOrgs(); - openDialogModal(data.createOrganization._id); - setFormState({ - name: '', - descrip: '', - userRegistrationRequired: true, - visible: false, - address: { - city: '', - countryCode: '', - dependentLocality: '', - line1: '', - line2: '', - postalCode: '', - sortingCode: '', - state: '', - }, - image: '', - }); - toggleModal(); - } - } catch (error: unknown) { - errorHandler(t, error); - } - }; + // const isAdminForCurrentOrg = ( + // currentOrg: InterfaceOrgConnectionInfoType, + // ): boolean => { + // if (adminFor.length === 1) { + // // If user is admin for one org only then check if that org is current org + // return adminFor[0]._id === currentOrg._id; + // } else { + // // If user is admin for more than one org then check if current org is present in adminFor array + // return ( + // adminFor.some( + // (org: { _id: string; name: string; image: string | null }) => + // org._id === currentOrg._id, + // ) ?? false + // ); + // } + // }; + + // const triggerCreateSampleOrg = (): void => { + // createSampleOrganization() + // .then(() => { + // toast.success(t('sampleOrgSuccess') as string); + // window.location.reload(); + // }) + // .catch(() => { + // toast.error(t('sampleOrgDuplicate') as string); + // }); + // }; + + // const createOrg = async (e: ChangeEvent): Promise => { + // e.preventDefault(); + + // const { + // name: _name, + // descrip: _descrip, + // address: _address, + // visible, + // userRegistrationRequired, + // image, + // } = formState; + + // const name = _name.trim(); + // const descrip = _descrip.trim(); + // const address = _address; + + // try { + // const { data } = await create({ + // variables: { + // name: name, + // description: descrip, + // address: address, + // visibleInSearch: visible, + // userRegistrationRequired: userRegistrationRequired, + // image: image, + // }, + // }); + // toggleModal; + // if (data) { + // toast.success('Congratulation the Organization is created'); + // refetchOrgs(); + // openDialogModal(data.createOrganization._id); + // setFormState({ + // name: '', + // descrip: '', + // userRegistrationRequired: true, + // visible: false, + // address: { + // city: '', + // countryCode: '', + // dependentLocality: '', + // line1: '', + // line2: '', + // postalCode: '', + // sortingCode: '', + // state: '', + // }, + // image: '', + // }); + // toggleModal(); + // } + // } catch (error: unknown) { + // errorHandler(t, error); + // } + // }; if (errorList || errorUser) { errorHandler(t, errorList || errorUser); @@ -285,35 +294,28 @@ function orgList(): JSX.Element { }; const loadMoreOrganizations = (): void => { - setIsLoadingMore(true); + if (!isLoadingMore || hasMore) setIsLoadingMore(true); fetchMore({ variables: { - skip: orgsData?.organizationsConnection.length || 0, + skip: orgsData?.edges?.length || 0, }, - updateQuery: ( - prev: - | { organizationsConnection: InterfaceOrgConnectionType[] } - | undefined, - { - fetchMoreResult, - }: { - fetchMoreResult: - | { organizationsConnection: InterfaceOrgConnectionType[] } - | undefined; - }, - ): - | { organizationsConnection: InterfaceOrgConnectionType[] } - | undefined => { + updateQuery: (prev, { fetchMoreResult }) => { setIsLoadingMore(false); - if (!fetchMoreResult) return prev; - if (fetchMoreResult.organizationsConnection.length < perPageResult) { - sethasMore(false); + + if (!fetchMoreResult || !fetchMoreResult.user) { + return prev; // Prevents breaking the UI } + return { - organizationsConnection: [ - ...(prev?.organizationsConnection || []), - ...(fetchMoreResult.organizationsConnection || []), - ], + user: { + organizationsWhereMember: { + pageInfo: fetchMoreResult.user.organizationsWhereMember.pageInfo, + edges: [ + ...(prev?.user.organizationsWhereMember.edges || []), + ...fetchMoreResult.user.organizationsWhereMember.edges, + ], + }, + }, }; }, }); @@ -357,7 +359,7 @@ function orgList(): JSX.Element { />
- {superAdmin && ( + {/* {superAdmin && ( - )} + )} */}
{/* Text Infos for list */} {!isLoading && - (!orgsData?.organizationsConnection || - orgsData.organizationsConnection.length === 0) && + (!orgsData?.edges || orgsData.edges.length === 0) && searchByName.length === 0 && - (!userData || adminFor.length === 0 || superAdmin) ? ( + (!userData || adminFor?.length === 0 || superAdmin) ? (

{t('noOrgErrorTitle')}

{t('noOrgErrorDescription')}
) : !isLoading && - orgsData?.organizationsConnection.length == 0 && + orgsData?.edges.length == 0 && searchByName.length > 0 ? (

@@ -426,26 +427,32 @@ function orgList(): JSX.Element { } > {userData && superAdmin - ? orgsData?.organizationsConnection.map( - (item: InterfaceOrgConnectionInfoType) => { + ? orgsData?.edges.map( + (item: InterfaceOrgConnectionInfoTypePG) => { return ( -
- +
+
); }, ) - : userData && - adminFor.length > 0 && - orgsData?.organizationsConnection.map( - (item: InterfaceOrgConnectionInfoType) => { - if (isAdminForCurrentOrg(item)) { - return ( -
- -
- ); - } + : // userData && + // adminFor.length > 0 && + orgsData?.edges.map( + (item: InterfaceOrgConnectionInfoTypePG) => { + // if (isAdminForCurrentOrg(item)) { + return ( +
+ +
+ ); + // } }, )} @@ -488,7 +495,7 @@ function orgList(): JSX.Element { * @param triggerCreateSampleOrg - A function to trigger the creation of a sample organization. * @returns JSX element representing the `OrganizationModal`. */} - + /> */} {/* Plugin Notification Modal after Org is Created */} - + {/* {t('goToStore')} - {/* */}
- {/* {superAdmin && ( + {role === 'administrator' && ( - )} */} + )}

@@ -377,7 +377,7 @@ function orgList(): JSX.Element { {!isLoading && (!orgsData?.edges || orgsData.edges.length === 0) && searchByName.length === 0 && - (!userData || adminFor?.length === 0 || superAdmin) ? ( + (!userData || adminFor?.length === 0) ? (

{t('noOrgErrorTitle')}

{t('noOrgErrorDescription')}
@@ -426,7 +426,7 @@ function orgList(): JSX.Element {
} > - {userData && superAdmin + {userData && role === 'administrator' ? orgsData?.edges.map( (item: InterfaceOrgConnectionInfoTypePG) => { return ( @@ -492,10 +492,10 @@ function orgList(): JSX.Element { * @param createOrg - A function to handle the submission of the organization creation form. * @param t - A translation function for localization. * @param userData - Information about the current user. - * @param triggerCreateSampleOrg - A function to trigger the creation of a sample organization. * @returns JSX element representing the `OrganizationModal`. */} - {/* */} + /> {/* Plugin Notification Modal after Org is Created */} - {/* + - */} + ); } diff --git a/src/screens/OrgList/OrganizationModal.spec.tsx b/src/screens/OrgList/OrganizationModal.spec.tsx new file mode 100644 index 0000000000..24da962a3d --- /dev/null +++ b/src/screens/OrgList/OrganizationModal.spec.tsx @@ -0,0 +1,465 @@ +import React from 'react'; +import type { RenderResult } from '@testing-library/react'; +import { + render, + screen, + fireEvent, + waitFor, + // within, +} from '@testing-library/react'; +import { vi } from 'vitest'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import { I18nextProvider } from 'react-i18next'; +import OrganizationModal from './OrganizationModal'; +import i18nForTest from 'utils/i18nForTest'; +import userEvent from '@testing-library/user-event'; + +vi.mock('utils/convertToBase64', () => ({ + default: vi.fn(() => Promise.resolve('mockBase64String')), +})); + +describe('OrganizationModal Component', () => { + const mockToggleModal = vi.fn(); + const mockCreateOrg = vi.fn((e) => e.preventDefault()); + const mockSetFormState = vi.fn(); + + const formState = { + addressLine1: '', + addressLine2: '', + avatar: '', + city: '', + countryCode: '', + description: '', + name: '', + postalCode: '', + state: '', + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + vi.mock('utils/convertToBase64', () => ({ + default: vi.fn((file) => { + if (file.size > 5000000) { + return Promise.reject(new Error('File too large')); + } + return Promise.resolve('mockBase64String'); + }), + })); + + const setup = (): RenderResult => { + return render( + + + + key} + tCommon={(key) => key} + userData={undefined} + /> + + + , + ); + }; + + test('renders OrganizationModal correctly', () => { + setup(); + expect(screen.getByTestId('modalOrganizationHeader')).toBeInTheDocument(); + expect(screen.getByTestId('modalOrganizationName')).toBeInTheDocument(); + expect(screen.getByTestId('submitOrganizationForm')).toBeInTheDocument(); + }); + + test('updates input fields correctly', async () => { + setup(); + const nameInput = screen.getByTestId('modalOrganizationName'); + fireEvent.change(nameInput, { target: { value: 'Test Organization' } }); + expect(mockSetFormState).toHaveBeenCalledWith( + expect.objectContaining({ name: 'Test Organization' }), + ); + }); + + test('submits form correctly', async () => { + setup(); + const submitButton = screen.getByTestId('submitOrganizationForm'); + fireEvent.click(submitButton); + await waitFor(() => expect(mockCreateOrg).toHaveBeenCalled()); + }); + + test('uploads image correctly', async () => { + setup(); + const fileInput = screen.getByTestId('organisationImage'); + const file = new File(['dummy content'], 'example.png', { + type: 'image/png', + }); + fireEvent.change(fileInput, { target: { files: [file] } }); + await waitFor(() => + expect(mockSetFormState).toHaveBeenCalledWith( + expect.objectContaining({ avatar: 'mockBase64String' }), + ), + ); + }); + + test('closes modal when close button is clicked', () => { + setup(); + const closeButton = screen.getByRole('button', { name: /close/i }); + fireEvent.click(closeButton); + expect(mockToggleModal).toHaveBeenCalled(); + }); + + test('triggers sample organization creation', async () => { + setup(); + fireEvent.click(screen.getByTestId('submitOrganizationForm')); + await waitFor(() => expect(mockCreateOrg).toHaveBeenCalled()); + }); + + test('updates all form fields correctly', () => { + setup(); + const fields = [ + { + testId: 'modalOrganizationDescription', + key: 'description', + value: 'A sample description', + }, + { testId: 'modalOrganizationCity', key: 'city', value: 'Sample City' }, + { testId: 'modalOrganizationState', key: 'state', value: 'Sample State' }, + { + testId: 'modalOrganizationPostalCode', + key: 'postalCode', + value: '123456', + }, + { + testId: 'modalOrganizationAddressLine1', + key: 'addressLine1', + value: '123 Street', + }, + { + testId: 'modalOrganizationAddressLine2', + key: 'addressLine2', + value: 'Apt 456', + }, + ]; + fields.forEach(({ testId, key, value }) => { + const input = screen.getByTestId(testId); + fireEvent.change(input, { target: { value } }); + expect(mockSetFormState).toHaveBeenCalledWith( + expect.objectContaining({ [key]: value }), + ); + }); + }); + + test('name field should not accept more than 50 characters', async () => { + setup(); + const nameInput = screen.getByTestId( + 'modalOrganizationName', + ) as HTMLInputElement; + const longText = 'a'.repeat(60); + + await userEvent.type(nameInput, longText); + + // Since the component limits input at 50 chars, we check the last setFormState call + const lastCall = + mockSetFormState.mock.calls[mockSetFormState.mock.calls.length - 1]; + expect(lastCall[0].name.length).toBeLessThanOrEqual(50); + }); + + test('description field should not accept more than 200 characters', async () => { + setup(); + const descInput = screen.getByTestId( + 'modalOrganizationDescription', + ) as HTMLInputElement; + const longText = 'a'.repeat(250); + + await userEvent.type(descInput, longText); + + // Check the last setFormState call + const lastCall = + mockSetFormState.mock.calls[mockSetFormState.mock.calls.length - 1]; + expect(lastCall[0].description.length).toBeLessThanOrEqual(200); + }); + + test('should handle country selection correctly', async () => { + setup(); + const countrySelect = screen.getByTestId( + 'modalOrganizationCountryCode', + ) as HTMLSelectElement; + fireEvent.change(countrySelect, { target: { value: 'us' } }); + + expect(mockSetFormState).toHaveBeenCalledWith( + expect.objectContaining({ + countryCode: 'us', + }), + ); + }); + + test('country select should have default disabled option', () => { + setup(); + const countrySelect = screen.getByTestId( + 'modalOrganizationCountryCode', + ) as HTMLSelectElement; + const firstOption = countrySelect.options[0]; + expect(firstOption.disabled).toBe(true); + }); + + test('should handle invalid file type', async () => { + setup(); + const fileInput = screen.getByTestId( + 'organisationImage', + ) as HTMLInputElement; + const invalidFile = new File(['dummy content'], 'test.txt', { + type: 'text/plain', + }); + + fireEvent.change(fileInput, { target: { files: [invalidFile] } }); + + expect(fileInput.files?.[0].type).not.toBe('image/png'); + }); + + test('form inputs should have associated labels', () => { + setup(); + expect(screen.getByLabelText(/name/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/description/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/displayImage/i)).toBeInTheDocument(); + }); + + test('required fields should have proper aria attributes', () => { + setup(); + const requiredInputs = screen + .getAllByRole('textbox', { hidden: true }) + .filter((input) => input instanceof HTMLInputElement && input.required); + + requiredInputs.forEach((input) => { + // Check if the input has either the required attribute or aria-required + expect( + input.hasAttribute('required') || + input.getAttribute('aria-required') === 'true', + ).toBeTruthy(); + }); + }); + + test('should handle form submission with all fields filled', async () => { + const setup = (): RenderResult => { + return render( + + + + key} + tCommon={(key) => key} + userData={undefined} + /> + + + , + ); + }; + const completeFormState = { + ...formState, + name: 'Test Organization', + description: 'Test Description', + addressLine1: '123 Test St', + city: 'Test City', + state: 'Test State', + countryCode: 'us', + postalCode: '12345', + }; + + setup(); + const submitButton = screen.getByTestId('submitOrganizationForm'); + + await userEvent.click(submitButton); + expect(mockCreateOrg).toHaveBeenCalled(); + }); + + const testCases = [ + { + fieldId: 'modalOrganizationName', + maxLength: 50, + formKey: 'name', + }, + { + fieldId: 'modalOrganizationDescription', + maxLength: 200, + formKey: 'description', + }, + { + fieldId: 'modalOrganizationState', + maxLength: 50, + formKey: 'state', + }, + { + fieldId: 'modalOrganizationCity', + maxLength: 50, + formKey: 'city', + }, + { + fieldId: 'modalOrganizationPostalCode', + maxLength: 50, + formKey: 'postalCode', + }, + { + fieldId: 'modalOrganizationAddressLine1', + maxLength: 50, + formKey: 'addressLine1', + }, + { + fieldId: 'modalOrganizationAddressLine2', + maxLength: 50, + formKey: 'addressLine2', + }, + ]; + + testCases.forEach(({ fieldId, maxLength, formKey }) => { + test(`${formKey} should not accept more than ${maxLength} characters`, async () => { + setup(); + const input = screen.getByTestId(fieldId); + const longText = 'a'.repeat(maxLength + 10); + // const expectedText = 'a'.repeat(maxLength - 1); + + await userEvent.type(input, longText); + + const lastCall = + mockSetFormState.mock.calls[mockSetFormState.mock.calls.length - 1]; + expect(lastCall[0][formKey].length).toBeLessThanOrEqual(maxLength); + expect(lastCall[0][formKey]).not.toEqual(longText); + }); + }); + test('should handle valid image upload', async () => { + setup(); + const file = new File(['dummy content'], 'test.png', { + type: 'image/png', + }); + const fileInput = screen.getByTestId('organisationImage'); + + await userEvent.upload(fileInput, file); + + expect(mockSetFormState).toHaveBeenCalledWith( + expect.objectContaining({ + avatar: 'mockBase64String', + }), + ); + }); + + test('should handle null file selection', async () => { + setup(); + const fileInput = screen.getByTestId('organisationImage'); + + // Simulate a file input change event with no files + fireEvent.change(fileInput, { target: { files: null } }); + + expect(mockSetFormState).not.toHaveBeenCalled(); + }); + + test('should handle empty file selection', async () => { + setup(); + const fileInput = screen.getByTestId('organisationImage'); + + // Simulate a file input change event with empty files array + fireEvent.change(fileInput, { target: { files: [] } }); + + expect(mockSetFormState).not.toHaveBeenCalled(); + }); + + test('should show modal when showModal is true', () => { + return render( + + + + key} + tCommon={(key) => key} + userData={undefined} + /> + + + , + ); + expect(screen.getByTestId('modalOrganizationHeader')).toBeVisible(); + }); + + test('should not show modal when showModal is false', () => { + return render( + + + + key} + tCommon={(key) => key} + userData={undefined} + /> + + + , + ); + expect( + screen.queryByTestId('modalOrganizationHeader'), + ).not.toBeInTheDocument(); + }); + + test('should call toggleModal when close button is clicked', async () => { + setup(); + const closeButton = screen.getByRole('button', { name: /close/i }); + await userEvent.click(closeButton); + expect(mockToggleModal).toHaveBeenCalled(); + }); + test('should handle country selection change', async () => { + setup(); + const countrySelect = screen.getByTestId('modalOrganizationCountryCode'); + + await userEvent.selectOptions(countrySelect, 'us'); + + expect(mockSetFormState).toHaveBeenCalledWith( + expect.objectContaining({ + countryCode: 'us', + }), + ); + }); + test('should validate all required fields on submit', async () => { + setup(); + const form = screen.getByTestId('submitOrganizationForm').closest('form'); + expect(form).toBeInTheDocument(); + + const requiredFields = [ + 'modalOrganizationName', + 'modalOrganizationDescription', + 'modalOrganizationState', + 'modalOrganizationCity', + 'modalOrganizationAddressLine1', + 'modalOrganizationCountryCode', + ]; + + // Verify all required fields are marked as required + requiredFields.forEach((fieldId) => { + const field = screen.getByTestId(fieldId); + expect(field).toBeRequired(); + }); + + if (form) { + await userEvent.click(screen.getByTestId('submitOrganizationForm')); + expect(mockCreateOrg).toHaveBeenCalled(); + } + }); +}); diff --git a/src/screens/OrgList/OrganizationModal.tsx b/src/screens/OrgList/OrganizationModal.tsx index 888a835d6e..b6721d79ca 100644 --- a/src/screens/OrgList/OrganizationModal.tsx +++ b/src/screens/OrgList/OrganizationModal.tsx @@ -3,12 +3,10 @@ import { Modal, Form, Row, Col, Button } from 'react-bootstrap'; import convertToBase64 from 'utils/convertToBase64'; import type { ChangeEvent } from 'react'; import styles from '../../style/app.module.css'; -import type { - InterfaceAddress, - InterfaceCurrentUserTypePG, -} from 'utils/interfaces'; +import type { InterfaceCurrentUserTypePG } from 'utils/interfaces'; import { countryOptions } from 'utils/formEnumFields'; -import useLocalStorage from 'utils/useLocalstorage'; + +// import useLocalStorage from 'utils/useLocalstorage'; /** * Represents the state of the form in the organization modal. @@ -33,19 +31,23 @@ import useLocalStorage from 'utils/useLocalstorage'; * * For more details on the reusable classes, refer to the global CSS file. */ + interface InterfaceFormStateType { + addressLine1: string; + addressLine2: string; + avatar: string | null; + city: string; + countryCode: string; + description: string; name: string; - descrip: string; - userRegistrationRequired: boolean; - visible: boolean; - address: InterfaceAddress; - image: string; + postalCode: string; + state: string; } /** * Represents the properties of the OrganizationModal component. */ -interface InterfaceOrganizationModalProps { +export interface InterfaceOrganizationModalProps { showModal: boolean; toggleModal: () => void; formState: InterfaceFormStateType; @@ -54,7 +56,6 @@ interface InterfaceOrganizationModalProps { t: (key: string) => string; tCommon: (key: string) => string; userData: InterfaceCurrentUserTypePG | undefined; - triggerCreateSampleOrg: () => void; } /** @@ -69,22 +70,7 @@ const OrganizationModal: React.FC = ({ createOrg, t, tCommon, - triggerCreateSampleOrg, }) => { - // function to update the state of the parameters inside address. - const { getItem } = useLocalStorage(); - const superAdmin = getItem('SuperAdmin'); - const adminFor = getItem('AdminFor'); - - const handleInputChange = (fieldName: string, value: string): void => { - setFormState((prevState) => ({ - ...prevState, - address: { - ...prevState.address, - [fieldName]: value, - }, - })); - }; return ( = ({ } }} /> - {tCommon('description')} + + + {tCommon('description')} + { const descriptionText = e.target.value; if (descriptionText.length < 200) { setFormState({ ...formState, - descrip: e.target.value, + description: e.target.value, }); } }} /> {tCommon('address')} - + { - const countryCode = e.target.value; - handleInputChange('countryCode', countryCode); + data-testid="modalOrganizationCountryCode" + value={formState.countryCode} + onChange={(e): void => { + const inputText = e.target.value; + if (inputText.length < 50) { + setFormState({ + ...formState, + countryCode: e.target.value, + }); + } }} className={`mb-3 ${styles.inputField}`} > @@ -161,64 +156,69 @@ const OrganizationModal: React.FC = ({ {countryOptions.map((country) => ( ))} - + handleInputChange('city', e.target.value)} + value={formState.state} + onChange={(e): void => { + const inputText = e.target.value; + if (inputText.length < 50) { + setFormState({ + ...formState, + state: e.target.value, + }); + } + }} className={`mb-3 ${styles.inputField}`} /> - - handleInputChange('state', e.target.value)} - className={`mb-3 ${styles.inputField}`} - /> - - - - handleInputChange('dependentLocality', e.target.value) - } - className={`mb-3 ${styles.inputField}`} - /> - - - handleInputChange('line1', e.target.value)} + value={formState.city} + onChange={(e): void => { + const inputText = e.target.value; + if (inputText.length < 50) { + setFormState({ + ...formState, + city: e.target.value, + }); + } + }} className={`mb-3 ${styles.inputField}`} /> handleInputChange('line2', e.target.value)} + value={formState.postalCode} + onChange={(e): void => { + const inputText = e.target.value; + if (inputText.length < 50) { + setFormState({ + ...formState, + postalCode: e.target.value, + }); + } + }} className={`mb-3 ${styles.inputField}`} /> @@ -226,66 +226,43 @@ const OrganizationModal: React.FC = ({ - handleInputChange('postalCode', e.target.value) - } + required + value={formState.addressLine1} + onChange={(e): void => { + const inputText = e.target.value; + if (inputText.length < 50) { + setFormState({ + ...formState, + addressLine1: e.target.value, + }); + } + }} className={`mb-3 ${styles.inputField}`} /> - handleInputChange('sortingCode', e.target.value) - } + value={formState.addressLine2} + onChange={(e): void => { + const inputText = e.target.value; + if (inputText.length < 50) { + setFormState({ + ...formState, + addressLine2: e.target.value, + }); + } + }} className={`mb-3 ${styles.inputField}`} /> - - - - {t('userRegistrationRequired')} - - - setFormState({ - ...formState, - userRegistrationRequired: - !formState.userRegistrationRequired, - }) - } - className={styles.switch} - /> - - - - {t('visibleInSearch')} - - - setFormState({ - ...formState, - visible: !formState.visible, - }) - } - className={styles.switch} - /> - - + {tCommon('displayImage')} = ({ if (file) setFormState({ ...formState, - image: await convertToBase64(file), + avatar: (await convertToBase64(file)) || null, }); }} data-testid="organisationImage" @@ -315,22 +292,6 @@ const OrganizationModal: React.FC = ({ > {t('createOrganization')} - -
-
- {tCommon('OR')} -
- {((adminFor && adminFor.length > 0) || superAdmin) && ( -
- -
- )} From a61d98305b9b298df5965030c17f9e1901232fa1 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Sun, 2 Feb 2025 02:36:29 +0530 Subject: [PATCH 8/9] CodeCoverage - UserPasswordUpdate done (#3519) * CodeCoverage - UserPasswordUpdate done * remove comments --- .../UserPasswordUpdate.spec.tsx | 88 ++++++++++++++++++- .../UserPasswordUpdate/UserPasswordUpdate.tsx | 3 - 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/components/UserPasswordUpdate/UserPasswordUpdate.spec.tsx b/src/components/UserPasswordUpdate/UserPasswordUpdate.spec.tsx index 0d704eaed8..8cd098d850 100644 --- a/src/components/UserPasswordUpdate/UserPasswordUpdate.spec.tsx +++ b/src/components/UserPasswordUpdate/UserPasswordUpdate.spec.tsx @@ -9,6 +9,7 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import { toast as mockToast } from 'react-toastify'; import { MOCKS } from './UserPasswordUpdateMocks'; import { vi } from 'vitest'; +import { UPDATE_USER_PASSWORD_MUTATION } from 'GraphQl/Mutations/mutations'; vi.mock('react-toastify', () => ({ toast: { @@ -121,6 +122,12 @@ describe('Testing User Password Update', () => { }); it('Successfully update old password', async () => { + const mockReload = vi.fn(); + Object.defineProperty(window, 'location', { + value: { reload: mockReload }, + writable: true, + }); + render( @@ -145,8 +152,18 @@ describe('Testing User Password Update', () => { ); userEvent.click(screen.getByText(/Save Changes/i)); - expect(mockToast.success).toHaveBeenCalledWith( - 'Password updated Successfully', + + await waitFor(() => { + expect(mockToast.success).toHaveBeenCalledWith( + 'Password updated Successfully', + ); + }); + + await waitFor( + () => { + expect(mockReload).toHaveBeenCalled(); + }, + { timeout: 3000 }, ); }); @@ -182,4 +199,71 @@ describe('Testing User Password Update', () => { ), ); }); + + it('network error', async () => { + const networkErrorMock = [ + { + request: { + query: UPDATE_USER_PASSWORD_MUTATION, + variables: { + previousPassword: 'NetworkErrorTest', + newPassword: 'ThePalisadoesFoundation', + confirmNewPassword: 'ThePalisadoesFoundation', + }, + }, + error: new Error('Network error'), + }, + ]; + + render( + + + + + , + ); + + userEvent.type( + screen.getByPlaceholderText(/Previous Password/i), + 'NetworkErrorTest', + ); + userEvent.type( + screen.getAllByPlaceholderText(/New Password/i)[0], + 'ThePalisadoesFoundation', + ); + userEvent.type( + screen.getByPlaceholderText(/Confirm New Password/i), + 'ThePalisadoesFoundation', + ); + + userEvent.click(screen.getByText(/Save Changes/i)); + + await waitFor( + () => { + expect(mockToast.error).toHaveBeenCalledWith( + 'ApolloError: Network error', + ); + }, + { timeout: 3000 }, + ); + }); + + it('reloads page when cancel button is clicked', async () => { + const mockReload = vi.fn(); + Object.defineProperty(window, 'location', { + value: { reload: mockReload }, + writable: true, + }); + + render( + + + + + , + ); + + userEvent.click(screen.getByText(/Cancel/i)); + expect(mockReload).toHaveBeenCalled(); + }); }); diff --git a/src/components/UserPasswordUpdate/UserPasswordUpdate.tsx b/src/components/UserPasswordUpdate/UserPasswordUpdate.tsx index c3e6c21248..556f0988a0 100644 --- a/src/components/UserPasswordUpdate/UserPasswordUpdate.tsx +++ b/src/components/UserPasswordUpdate/UserPasswordUpdate.tsx @@ -62,7 +62,6 @@ const UserUpdate: React.FC< confirmNewPassword: formState.confirmNewPassword, }, }); - /* istanbul ignore next */ if (data) { toast.success( tCommon('updatedSuccessfully', { item: 'Password' }) as string, @@ -72,7 +71,6 @@ const UserUpdate: React.FC< }, 2000); } } catch (error: unknown) { - /* istanbul ignore next */ if (error instanceof Error) { toast.error(error.toString()); } @@ -83,7 +81,6 @@ const UserUpdate: React.FC< * Handles canceling the update process. * It reloads the page to reset any changes. */ - /* istanbul ignore next */ const cancelUpdate = (): void => { window.location.reload(); }; From 735869e453d00489dd6212f5a6f5ff357023c66b Mon Sep 17 00:00:00 2001 From: Jai Pannu <142983705+JaiPannu-IITI@users.noreply.github.com> Date: Sat, 1 Feb 2025 22:15:32 +0000 Subject: [PATCH 9/9] Fix: Missing & Incorrect API URLs in .env.example (#3512) * .env.example updated * Script fixed * url correction * code coverage for url prompt --- .env.example | 4 +- .../checkEnvFile/functions/checkEnvFile.md | 4 +- .../checkEnvFile/functions/modifyEnvFile.md | 13 +++++ .../docs/docs/getting-started/installation.md | 12 ++--- setup.ts | 11 +++- .../askForTalawaApiUrl.spec.ts | 29 ++++++++-- .../askForTalawaApiUrl/askForTalawaApiUrl.ts | 7 ++- .../setupTalawaWebSocketUrl.spec.ts | 2 +- .../checkConnection/checkConnection.spec.ts | 6 +-- src/setup/checkEnvFile/checkEnvFile.spec.ts | 54 +++++++++++++++++-- src/setup/checkEnvFile/checkEnvFile.ts | 14 ++++- 11 files changed, 127 insertions(+), 29 deletions(-) create mode 100644 docs/docs/auto-docs/setup/checkEnvFile/checkEnvFile/functions/modifyEnvFile.md diff --git a/.env.example b/.env.example index de7cff92ec..4c2afd6983 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,7 @@ PORT=4321 # Run Talawa-api locally in your system, and put its url into the same. -REACT_APP_TALAWA_URL= +REACT_APP_TALAWA_URL=http://localhost:4000/graphql # Do you want to setup and use "I'm not a robot" Checkbox (Google Recaptcha)? # If no, leave blank, else write yes @@ -24,7 +24,7 @@ REACT_APP_USE_RECAPTCHA= REACT_APP_RECAPTCHA_SITE_KEY= # has to be inserted in the env file to use plugins and other websocket based features. -REACT_APP_BACKEND_WEBSOCKET_URL=ws://localhost:4000/graphql/ +REACT_APP_BACKEND_WEBSOCKET_URL=ws://localhost:4000/graphql # If you want to logs Compiletime and Runtime error , warning and info write YES or if u want to # keep the console clean leave it blank diff --git a/docs/docs/auto-docs/setup/checkEnvFile/checkEnvFile/functions/checkEnvFile.md b/docs/docs/auto-docs/setup/checkEnvFile/checkEnvFile/functions/checkEnvFile.md index 650a7f1104..c92bccdc38 100644 --- a/docs/docs/auto-docs/setup/checkEnvFile/checkEnvFile/functions/checkEnvFile.md +++ b/docs/docs/auto-docs/setup/checkEnvFile/checkEnvFile/functions/checkEnvFile.md @@ -4,10 +4,10 @@ # Function: checkEnvFile() -> **checkEnvFile**(): `void` +> **checkEnvFile**(): `boolean` Defined in: [src/setup/checkEnvFile/checkEnvFile.ts:6](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/setup/checkEnvFile/checkEnvFile.ts#L6) ## Returns -`void` +`boolean` diff --git a/docs/docs/auto-docs/setup/checkEnvFile/checkEnvFile/functions/modifyEnvFile.md b/docs/docs/auto-docs/setup/checkEnvFile/checkEnvFile/functions/modifyEnvFile.md new file mode 100644 index 0000000000..a7643ec8fa --- /dev/null +++ b/docs/docs/auto-docs/setup/checkEnvFile/checkEnvFile/functions/modifyEnvFile.md @@ -0,0 +1,13 @@ +[Admin Docs](/) + +*** + +# Function: modifyEnvFile() + +> **modifyEnvFile**(): `void` + +Defined in: [src/setup/checkEnvFile/checkEnvFile.ts:18](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/setup/checkEnvFile/checkEnvFile.ts#L18) + +## Returns + +`void` diff --git a/docs/docs/docs/getting-started/installation.md b/docs/docs/docs/getting-started/installation.md index c033fb76b8..7114669c50 100644 --- a/docs/docs/docs/getting-started/installation.md +++ b/docs/docs/docs/getting-started/installation.md @@ -202,21 +202,21 @@ Add a custom port number for Talawa-Admin development purposes to the variable n Add the endpoint for accessing talawa-api graphql service to the variable named `REACT_APP_TALAWA_URL` in the `.env` file. ``` -REACT_APP_TALAWA_URL="http://API-IP-ADRESS:4000/graphql/" +REACT_APP_TALAWA_URL="http://API-IP-ADRESS:4000/graphql" ``` If you are a software developer working on your local system, then the URL would be: ``` -REACT_APP_TALAWA_URL="http://localhost:4000/graphql/" +REACT_APP_TALAWA_URL="http://localhost:4000/graphql" ``` If you are trying to access Talawa Admin from a remote host with the API URL containing "localhost", You will have to change the API URL to ``` -REACT_APP_TALAWA_URL="http://YOUR-REMOTE-ADDRESS:4000/graphql/" +REACT_APP_TALAWA_URL="http://YOUR-REMOTE-ADDRESS:4000/graphql" ``` @@ -225,21 +225,21 @@ REACT_APP_TALAWA_URL="http://YOUR-REMOTE-ADDRESS:4000/graphql/" The endpoint for accessing talawa-api WebSocket graphql service for handling subscriptions is automatically added to the variable named `REACT_APP_BACKEND_WEBSOCKET_URL` in the `.env` file. ``` -REACT_APP_BACKEND_WEBSOCKET_URL="ws://API-IP-ADRESS:4000/graphql/" +REACT_APP_BACKEND_WEBSOCKET_URL="ws://API-IP-ADRESS:4000/graphql" ``` If you are a software developer working on your local system, then the URL would be: ``` -REACT_APP_BACKEND_WEBSOCKET_URL="ws://localhost:4000/graphql/" +REACT_APP_BACKEND_WEBSOCKET_URL="ws://localhost:4000/graphql" ``` If you are trying to access Talawa Admin from a remote host with the API URL containing "localhost", You will have to change the API URL to ``` -REACT_APP_BACKEND_WEBSOCKET_URL="ws://YOUR-REMOTE-ADDRESS:4000/graphql/" +REACT_APP_BACKEND_WEBSOCKET_URL="ws://YOUR-REMOTE-ADDRESS:4000/graphql" ``` diff --git a/setup.ts b/setup.ts index 2c39924be8..f43c62d46f 100644 --- a/setup.ts +++ b/setup.ts @@ -1,7 +1,10 @@ import dotenv from 'dotenv'; import fs from 'fs'; import inquirer from 'inquirer'; -import { checkEnvFile } from './src/setup/checkEnvFile/checkEnvFile'; +import { + checkEnvFile, + modifyEnvFile, +} from './src/setup/checkEnvFile/checkEnvFile'; import { validateRecaptcha } from './src/setup/validateRecaptcha/validateRecaptcha'; import askAndSetDockerOption from './src/setup/askAndSetDockerOption/askAndSetDockerOption'; import updateEnvFile from './src/setup/updateEnvFile/updateEnvFile'; @@ -59,9 +62,13 @@ const askAndSetLogErrors = async (): Promise => { // Main function to run the setup process export async function main(): Promise { try { + if (!checkEnvFile()) { + return; + } + console.log('Welcome to the Talawa Admin setup! 🚀'); - checkEnvFile(); + modifyEnvFile(); await askAndSetDockerOption(); const envConfig = dotenv.parse(fs.readFileSync('.env', 'utf8')); const useDocker = envConfig.USE_DOCKER === 'YES'; diff --git a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.spec.ts b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.spec.ts index bceecb806f..3d82a4a488 100644 --- a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.spec.ts +++ b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.spec.ts @@ -16,6 +16,25 @@ describe('askForTalawaApiUrl', () => { }); it('should return the provided endpoint when user enters it', async () => { + const mockPrompt = vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ + endpoint: 'http://example.com/graphql', + }); + + const result = await askForTalawaApiUrl(); + + expect(mockPrompt).toHaveBeenCalledWith([ + { + type: 'input', + name: 'endpoint', + message: 'Enter your talawa-api endpoint:', + default: 'http://localhost:4000/graphql', + }, + ]); + + expect(result).toBe('http://example.com/graphql'); + }); + + it('should return the corrected endpoint when user enters with trailing slash', async () => { const mockPrompt = vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ endpoint: 'http://example.com/graphql/', }); @@ -27,16 +46,16 @@ describe('askForTalawaApiUrl', () => { type: 'input', name: 'endpoint', message: 'Enter your talawa-api endpoint:', - default: 'http://localhost:4000/graphql/', + default: 'http://localhost:4000/graphql', }, ]); - expect(result).toBe('http://example.com/graphql/'); + expect(result).toBe('http://example.com/graphql'); }); it('should return the default endpoint when the user does not enter anything', async () => { const mockPrompt = vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ - endpoint: 'http://localhost:4000/graphql/', + endpoint: 'http://localhost:4000/graphql', }); const result = await askForTalawaApiUrl(); @@ -46,10 +65,10 @@ describe('askForTalawaApiUrl', () => { type: 'input', name: 'endpoint', message: 'Enter your talawa-api endpoint:', - default: 'http://localhost:4000/graphql/', + default: 'http://localhost:4000/graphql', }, ]); - expect(result).toBe('http://localhost:4000/graphql/'); + expect(result).toBe('http://localhost:4000/graphql'); }); }); diff --git a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts index 713ed7dc68..0d00dccc0c 100644 --- a/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts +++ b/src/setup/askForTalawaApiUrl/askForTalawaApiUrl.ts @@ -6,8 +6,11 @@ export async function askForTalawaApiUrl(): Promise { type: 'input', name: 'endpoint', message: 'Enter your talawa-api endpoint:', - default: 'http://localhost:4000/graphql/', + default: 'http://localhost:4000/graphql', }, ]); - return endpoint; + + const correctEndpoint = endpoint.replace(/\/graphql\/$/, '/graphql'); + + return correctEndpoint; } diff --git a/src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.spec.ts b/src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.spec.ts index 926444d8a6..5c48e66bcd 100644 --- a/src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.spec.ts +++ b/src/setup/askForTalawaApiUrl/setupTalawaWebSocketUrl.spec.ts @@ -33,7 +33,7 @@ describe('WebSocket URL Configuration', () => { test('should retain default WebSocket URL if no new endpoint is provided', async () => { vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ - endpoint: 'http://localhost:4000/graphql/', + endpoint: 'http://localhost:4000/graphql', }); await askForTalawaApiUrl(); diff --git a/src/setup/checkConnection/checkConnection.spec.ts b/src/setup/checkConnection/checkConnection.spec.ts index 724f1a49dd..77eb77bfa8 100644 --- a/src/setup/checkConnection/checkConnection.spec.ts +++ b/src/setup/checkConnection/checkConnection.spec.ts @@ -3,7 +3,7 @@ import { vi, describe, beforeEach, it, expect } from 'vitest'; vi.mock('node-fetch'); global.fetch = vi.fn((url) => { - if (url === 'http://example.com/graphql/') { + if (url === 'http://example.com/graphql') { const responseInit: ResponseInit = { status: 200, statusText: 'OK', @@ -27,7 +27,7 @@ describe('checkConnection', () => { test('should return true and log success message if the connection is successful', async () => { vi.spyOn(console, 'log').mockImplementation((string) => string); - const result = await checkConnection('http://example.com/graphql/'); + const result = await checkConnection('http://example.com/graphql'); expect(result).toBe(true); expect(console.log).toHaveBeenCalledWith( @@ -41,7 +41,7 @@ describe('checkConnection', () => { it('should return false and log error message if the connection fails', async () => { vi.spyOn(console, 'log').mockImplementation((string) => string); const result = await checkConnection( - 'http://example_not_working.com/graphql/', + 'http://example_not_working.com/graphql', ); expect(result).toBe(false); diff --git a/src/setup/checkEnvFile/checkEnvFile.spec.ts b/src/setup/checkEnvFile/checkEnvFile.spec.ts index c2b75db0a6..2192f38d43 100644 --- a/src/setup/checkEnvFile/checkEnvFile.spec.ts +++ b/src/setup/checkEnvFile/checkEnvFile.spec.ts @@ -1,9 +1,9 @@ import fs from 'fs'; -import { checkEnvFile } from './checkEnvFile'; +import { checkEnvFile, modifyEnvFile } from './checkEnvFile'; import { vi } from 'vitest'; /** - * This file contains unit tests for the `checkEnvFile` function. + * This file contains unit tests for the `modifyEnvFile` function. * * The tests cover: * - Behavior when the `.env` file is missing required keys and appending them appropriately. @@ -14,7 +14,7 @@ import { vi } from 'vitest'; vi.mock('fs'); -describe('checkEnvFile', () => { +describe('modifyEnvFile', () => { beforeEach(() => { vi.resetAllMocks(); }); @@ -31,7 +31,7 @@ describe('checkEnvFile', () => { vi.spyOn(fs, 'appendFileSync'); - checkEnvFile(); + modifyEnvFile(); expect(fs.appendFileSync).toHaveBeenCalledWith( '.env', @@ -49,8 +49,52 @@ describe('checkEnvFile', () => { vi.spyOn(fs, 'appendFileSync'); - checkEnvFile(); + modifyEnvFile(); expect(fs.appendFileSync).not.toHaveBeenCalled(); }); }); + +describe('checkEnvFile', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + it('should return true if .env file already exists', () => { + vi.spyOn(fs, 'existsSync').mockImplementation((file) => file === '.env'); + + const result = checkEnvFile(); + + expect(result).toBe(true); + }); + + it('should create .env if it does not exist but .env.example exists', () => { + vi.spyOn(fs, 'existsSync').mockImplementation( + (file) => file === '.env.example', + ); + + const writeFileSyncMock = vi + .spyOn(fs, 'writeFileSync') + .mockImplementation(() => {}); + + const result = checkEnvFile(); + + expect(writeFileSyncMock).toHaveBeenCalledWith('.env', '', 'utf8'); + expect(result).toBe(true); + }); + + it('should return false and log an error if .env and .env.example do not exist', () => { + vi.spyOn(fs, 'existsSync').mockImplementation(() => false); + const consoleErrorMock = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + const result = checkEnvFile(); + + expect(consoleErrorMock).toHaveBeenCalledWith( + 'Setup requires .env.example to proceed.\n', + ); + expect(result).toBe(false); + expect(fs.writeFileSync).not.toHaveBeenCalled(); + }); +}); diff --git a/src/setup/checkEnvFile/checkEnvFile.ts b/src/setup/checkEnvFile/checkEnvFile.ts index 420a7c1321..5b3630247c 100644 --- a/src/setup/checkEnvFile/checkEnvFile.ts +++ b/src/setup/checkEnvFile/checkEnvFile.ts @@ -3,7 +3,19 @@ import fs from 'fs'; dotenv.config(); -export function checkEnvFile(): void { +export function checkEnvFile(): boolean { + if (!fs.existsSync('.env')) { + if (fs.existsSync('.env.example')) { + fs.writeFileSync('.env', '', 'utf8'); + } else { + console.error('Setup requires .env.example to proceed.\n'); + return false; + } + } + return true; +} + +export function modifyEnvFile(): void { const env = dotenv.parse(fs.readFileSync('.env')); const envSample = dotenv.parse(fs.readFileSync('.env.example')); const misplaced = Object.keys(envSample).filter((key) => !(key in env));