diff --git a/.github/workflows/check-tsdoc.js b/.github/workflows/check-tsdoc.js index 0400f5a108..d5c3b33b90 100644 --- a/.github/workflows/check-tsdoc.js +++ b/.github/workflows/check-tsdoc.js @@ -23,7 +23,6 @@ async function findTsxFiles(dir) { } else if ( filePath.endsWith('.tsx') && !filePath.endsWith('.test.tsx') && - !filePath.endsWith('.spec.tsx') && !filesToSkip.includes(path.relative(dir, filePath)) ) { results.push(filePath); diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 5096758ea5..668d618fdf 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -49,7 +49,7 @@ jobs: - name: Run formatting if check fails if: failure() - run: npm run format:fix + run: npm run format - name: Check for type errors if: steps.changed-files.outputs.only_changed != 'true' diff --git a/src/screens/EventVolunteers/Requests/Requests.spec.tsx b/src/screens/EventVolunteers/Requests/Requests.test.tsx similarity index 92% rename from src/screens/EventVolunteers/Requests/Requests.spec.tsx rename to src/screens/EventVolunteers/Requests/Requests.test.tsx index e51e28ab3f..3b55ea872c 100644 --- a/src/screens/EventVolunteers/Requests/Requests.spec.tsx +++ b/src/screens/EventVolunteers/Requests/Requests.test.tsx @@ -1,10 +1,3 @@ -/** - * Testing component for managing and displaying Volunteer Membership requests for an event. - * - * This component allows users to view, filter, sort, and create action items. It also allows users to accept or reject volunteer membership requests. - * - * - */ import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { LocalizationProvider } from '@mui/x-date-pickers'; @@ -27,12 +20,11 @@ import { UPDATE_ERROR_MOCKS, } from './Requests.mocks'; import { toast } from 'react-toastify'; -import { vi } from 'vitest'; -vi.mock('react-toastify', () => ({ +jest.mock('react-toastify', () => ({ toast: { - success: vi.fn(), - error: vi.fn(), + success: jest.fn(), + error: jest.fn(), }, })); @@ -82,14 +74,14 @@ const renderRequests = (link: ApolloLink): RenderResult => { describe('Testing Requests Screen', () => { beforeAll(() => { - vi.mock('react-router-dom', async () => ({ - ...(await vi.importActual('react-router-dom')), + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), useParams: () => ({ orgId: 'orgId', eventId: 'eventId' }), })); }); afterAll(() => { - vi.clearAllMocks(); + jest.clearAllMocks(); }); it('should redirect to fallback URL if URL params are undefined', async () => { @@ -110,7 +102,10 @@ describe('Testing Requests Screen', () => { , ); - expect(window.location.pathname).toBe('/'); + + await waitFor(() => { + expect(screen.getByTestId('paramsError')).toBeInTheDocument(); + }); }); it('should render Requests screen', async () => { diff --git a/src/screens/LoginPage/LoginPage.module.css b/src/screens/LoginPage/LoginPage.module.css deleted file mode 100644 index 6f58138c34..0000000000 --- a/src/screens/LoginPage/LoginPage.module.css +++ /dev/null @@ -1,269 +0,0 @@ -.login_background { - min-height: 100vh; -} - -.communityLogo { - object-fit: contain; -} - -.row .left_portion { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - height: 100vh; -} - -.selectOrgText input { - outline: none !important; -} - -.row .left_portion .inner .palisadoes_logo { - width: 600px; - height: auto; -} - -.row .right_portion { - min-height: 100vh; - position: relative; - overflow-y: scroll; - display: flex; - flex-direction: column; - justify-content: center; - padding: 1rem 2.5rem; - background: var(--bs-white); -} - -.row .right_portion::-webkit-scrollbar { - display: none; -} - -.row .right_portion .langChangeBtn { - margin: 0; - position: absolute; - top: 1rem; - left: 1rem; -} - -.langChangeBtnStyle { - width: 7.5rem; - height: 2.2rem; - padding: 0; -} - -.row .right_portion .talawa_logo { - height: 5rem; - width: 5rem; - display: block; - margin: 1.5rem auto 1rem; - -webkit-animation: zoomIn 0.3s ease-in-out; - animation: zoomIn 0.3s ease-in-out; -} - -.row .orText { - display: block; - position: absolute; - top: 0; - left: calc(50% - 2.6rem); - margin: 0 auto; - padding: 0.35rem 2rem; - z-index: 100; - background: var(--bs-white); - color: var(--bs-secondary); -} - -.email_button { - position: absolute; - z-index: 10; - bottom: 0; - right: 0; - height: 100%; - display: flex; - background-color: var(--search-button-bg); - border-color: var(--search-button-border); - justify-content: center; - align-items: center; -} - -.login_btn { - background-color: var(--search-button-bg); - border-color: var(--search-button-border); - margin-top: 1rem; - /* mt-3: Bootstrap margin spacing utility (3 = 1rem) */ - margin-bottom: 1rem; - /* mb-3: Bootstrap margin spacing utility (3 = 1rem) */ - width: 100%; -} - -.reg_btn { - background-color: var(--dropdown-border-color); - border-color: var(--dropdown-border-color); - margin-top: 1rem; - color: white; - /* mt-3: Bootstrap margin spacing utility (3 = 1rem) */ - margin-bottom: 1rem; - /* mb-3: Bootstrap margin spacing utility (3 = 1rem) */ - width: 100%; -} - -@media (max-width: 992px) { - .row .left_portion { - padding: 0 2rem; - } - - .row .left_portion .inner .palisadoes_logo { - width: 100%; - } -} - -@media (max-width: 769px) { - .row { - flex-direction: column-reverse; - } - - .row .right_portion, - .row .left_portion { - height: unset; - } - - .row .right_portion { - min-height: 100vh; - overflow-y: unset; - } - - .row .left_portion .inner { - display: flex; - justify-content: center; - } - - .row .left_portion .inner .palisadoes_logo { - height: 70px; - width: unset; - position: absolute; - margin: 0.5rem; - top: 0; - right: 0; - z-index: 100; - } - - .row .left_portion .inner p { - margin-bottom: 0; - padding: 1rem; - } - - .socialIcons { - margin-bottom: 1rem; - } -} - -@media (max-width: 577px) { - .row .right_portion { - padding: 1rem 1rem 0 1rem; - } - - .row .right_portion .langChangeBtn { - position: absolute; - margin: 1rem; - left: 0; - top: 0; - } - - .marginTopForReg { - margin-top: 4rem !important; - } - - .row .right_portion .talawa_logo { - height: 120px; - margin: 0 auto 2rem auto; - } - - .socialIcons { - margin-bottom: 1rem; - } -} - -.active_tab { - -webkit-animation: fadeIn 0.3s ease-in-out; - animation: fadeIn 0.3s ease-in-out; -} - -@-webkit-keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@-webkit-keyframes fadeIn { - 0% { - opacity: 0; - -webkit-transform: translateY(2rem); - transform: translateY(2rem); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes fadeIn { - 0% { - opacity: 0; - -webkit-transform: translateY(2rem); - transform: translateY(2rem); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -.socialIcons { - display: flex; - gap: 16px; - justify-content: center; -} - -.password_checks { - display: flex; - justify-content: space-between; - align-items: flex-start; - flex-direction: column; -} - -.password_check_element { - margin-top: -10px; -} - -.password_check_element_top { - margin-top: 18px; -} - -.password_check_element_bottom { - margin-bottom: -20px; -} diff --git a/src/screens/LoginPage/LoginPage.tsx b/src/screens/LoginPage/LoginPage.tsx index c68ecaceb0..bc22fd9615 100644 --- a/src/screens/LoginPage/LoginPage.tsx +++ b/src/screens/LoginPage/LoginPage.tsx @@ -30,7 +30,7 @@ import LoginPortalToggle from 'components/LoginPortalToggle/LoginPortalToggle'; import { errorHandler } from 'utils/errorHandler'; import useLocalStorage from 'utils/useLocalstorage'; import { socialMediaLinks } from '../../constants'; -import styles from './LoginPage.module.css'; +import styles from 'style/app.module.css'; import type { InterfaceQueryOrganizationListObject } from 'utils/interfaces'; import { Autocomplete, TextField } from '@mui/material'; import useSession from 'utils/useSession'; diff --git a/src/screens/OrgSettings/OrgSettings.spec.tsx b/src/screens/OrgSettings/OrgSettings.test.tsx similarity index 54% rename from src/screens/OrgSettings/OrgSettings.spec.tsx rename to src/screens/OrgSettings/OrgSettings.test.tsx index 5b5179d644..a9aec5f33d 100644 --- a/src/screens/OrgSettings/OrgSettings.spec.tsx +++ b/src/screens/OrgSettings/OrgSettings.test.tsx @@ -1,38 +1,28 @@ -import type { ReactElement } from 'react'; import React from 'react'; -import { describe, it, expect, vi } from 'vitest'; +import { MockedProvider } from '@apollo/react-testing'; +import type { RenderResult } from '@testing-library/react'; import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { Provider } from 'react-redux'; +import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { MockedProvider } from '@apollo/react-testing'; +import { Provider } from 'react-redux'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; + import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; +import i18nForTest from 'utils/i18nForTest'; import OrgSettings from './OrgSettings'; +import userEvent from '@testing-library/user-event'; +import type { ApolloLink } from '@apollo/client'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { MOCKS } from './OrgSettings.mocks'; const link1 = new StaticMockLink(MOCKS); -const mockRouterParams = (orgId: string | undefined): void => { - vi.doMock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { - ...actual, - useParams: () => ({ orgId }), - }; - }); -}; -const renderOrganisationSettings = ( - link = link1, - orgId = 'orgId', -): ReturnType => { - mockRouterParams(orgId); + +const renderOrganisationSettings = (link: ApolloLink): RenderResult => { return render( - + @@ -40,9 +30,7 @@ const renderOrganisationSettings = ( } /> Redirected to Home - } + element={
} />
@@ -54,37 +42,28 @@ const renderOrganisationSettings = ( }; describe('Organisation Settings Page', () => { - afterEach(() => { - vi.unmock('react-router-dom'); + beforeAll(() => { + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: 'orgId' }), + })); }); - const SetupRedirectTest = async (): Promise => { - const useParamsMock = vi.fn(() => ({ orgId: undefined })); - vi.doMock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { - ...actual, - useParams: useParamsMock, - }; - }); - const orgSettingsModule = await import('./OrgSettings'); - return ; - }; + afterAll(() => { + jest.clearAllMocks(); + }); it('should redirect to fallback URL if URL params are undefined', async () => { - const OrgSettings = await SetupRedirectTest(); render( - + - + } /> Redirected to Home - } + element={
} />
@@ -92,16 +71,13 @@ describe('Organisation Settings Page', () => {
, ); - await waitFor(() => { - const paramsErrorElement = screen.getByTestId('paramsError'); - expect(paramsErrorElement).toBeInTheDocument(); - expect(paramsErrorElement.textContent).toBe('Redirected to Home'); + expect(screen.getByTestId('paramsError')).toBeInTheDocument(); }); }); - it('should render the organisation settings page', async () => { - renderOrganisationSettings(); + test('should render the organisation settings page', async () => { + renderOrganisationSettings(link1); await waitFor(() => { expect(screen.getByTestId('generalSettings')).toBeInTheDocument(); @@ -112,11 +88,10 @@ describe('Organisation Settings Page', () => { screen.getByTestId('agendaItemCategoriesSettings'), ).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('generalSettings')); + await waitFor(() => { expect(screen.getByTestId('generalTab')).toBeInTheDocument(); - expect(screen.getByTestId('generalTab')).toBeVisible(); }); userEvent.click(screen.getByTestId('actionItemCategoriesSettings')); @@ -129,19 +104,4 @@ describe('Organisation Settings Page', () => { expect(screen.getByTestId('agendaItemCategoriesTab')).toBeInTheDocument(); }); }); - - it('should render dropdown for settings tabs', async () => { - renderOrganisationSettings(); - - await waitFor(() => { - expect(screen.getByTestId('settingsDropdownToggle')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByTestId('settingsDropdownToggle')); - - const dropdownItems = screen.getAllByRole('button', { - name: /general|actionItemCategories|agendaItemCategories/i, - }); - expect(dropdownItems).toHaveLength(3); - }); }); diff --git a/src/screens/OrganizationDashboard/OrganizationDashboard.module.css b/src/screens/OrganizationDashboard/OrganizationDashboard.module.css new file mode 100644 index 0000000000..3ffe274196 --- /dev/null +++ b/src/screens/OrganizationDashboard/OrganizationDashboard.module.css @@ -0,0 +1,35 @@ +.cardHeader { + padding: 1.25rem 1rem 1rem 1rem; + border-bottom: 1px solid var(--bs-gray-200); + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.cardHeader .cardTitle { + font-size: 1.2rem; + font-weight: 600; +} + +.cardBody { + min-height: 180px; + padding-top: 0; + max-height: 570px; + overflow-y: scroll; + width: 100%; + max-width: 400px; +} + +.cardBody .emptyContainer { + display: flex; + height: 180px; + justify-content: center; + align-items: center; +} + +.rankings { + aspect-ratio: 1; + border-radius: 50%; + width: 35px; +} diff --git a/src/screens/OrganizationDashboard/OrganizationDashboard.tsx b/src/screens/OrganizationDashboard/OrganizationDashboard.tsx index abc712289c..ebea874d2e 100644 --- a/src/screens/OrganizationDashboard/OrganizationDashboard.tsx +++ b/src/screens/OrganizationDashboard/OrganizationDashboard.tsx @@ -31,7 +31,7 @@ import type { InterfaceQueryOrganizationsListObject, InterfaceVolunteerRank, } from 'utils/interfaces'; -import styles from 'style/app.module.css'; +import styles from './OrganizationDashboard.module.css'; import { VOLUNTEER_RANKING } from 'GraphQl/Queries/EventVolunteerQueries'; /** @@ -41,7 +41,7 @@ import { VOLUNTEER_RANKING } from 'GraphQl/Queries/EventVolunteerQueries'; * * @returns The rendered component. */ -function OrganizationDashboard(): JSX.Element { +function organizationDashboard(): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'dashboard' }); const { t: tCommon } = useTranslation('common'); const { t: tErrors } = useTranslation('errors'); @@ -299,7 +299,7 @@ function OrganizationDashboard(): JSX.Element { {t('viewAll')} - + {loadingEvent ? ( [...Array(4)].map((_, index) => { return ; @@ -341,7 +341,7 @@ function OrganizationDashboard(): JSX.Element { {t('viewAll')} - + {loadingPost ? ( [...Array(4)].map((_, index) => { return ; @@ -392,7 +392,7 @@ function OrganizationDashboard(): JSX.Element { {loadingOrgData ? ( @@ -435,10 +435,7 @@ function OrganizationDashboard(): JSX.Element { {t('viewAll')} - + {rankingsLoading ? ( [...Array(3)].map((_, index) => { return ; @@ -486,4 +483,4 @@ function OrganizationDashboard(): JSX.Element { ); } -export default OrganizationDashboard; +export default organizationDashboard; diff --git a/src/screens/Requests/Requests.spec.tsx b/src/screens/Requests/Requests.test.tsx similarity index 91% rename from src/screens/Requests/Requests.spec.tsx rename to src/screens/Requests/Requests.test.tsx index 820b24c40d..4606fdae08 100644 --- a/src/screens/Requests/Requests.spec.tsx +++ b/src/screens/Requests/Requests.test.tsx @@ -1,6 +1,8 @@ import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { render, screen } from '@testing-library/react'; +import 'jest-localstorage-mock'; +import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -20,34 +22,6 @@ import { MOCKS4, } from './RequestsMocks'; import useLocalStorage from 'utils/useLocalstorage'; -import { vi } from 'vitest'; - -/** - * Set up `localStorage` stubs for testing. - */ - -vi.stubGlobal('localStorage', { - getItem: vi.fn(), - setItem: vi.fn(), - clear: vi.fn(), - removeItem: vi.fn(), -}); - -/** - * Mock `window.location` for testing redirection behavior. - */ - -Object.defineProperty(window, 'location', { - value: { - href: 'http://localhost/', - assign: vi.fn(), - reload: vi.fn(), - pathname: '/', - search: '', - hash: '', - origin: 'http://localhost', - }, -}); const { setItem, removeItem } = useLocalStorage(); @@ -59,14 +33,6 @@ const link5 = new StaticMockLink(MOCKS_WITH_ERROR, true); const link6 = new StaticMockLink(MOCKS3, true); const link7 = new StaticMockLink(MOCKS4, true); -/** - * Utility function to wait for a specified amount of time. - * Wraps `setTimeout` in an `act` block for testing purposes. - * - * @param ms - The duration to wait in milliseconds. Default is 100ms. - * @returns A promise that resolves after the specified time. - */ - async function wait(ms = 100): Promise { await act(() => { return new Promise((resolve) => { @@ -87,6 +53,7 @@ afterEach(() => { describe('Testing Requests screen', () => { test('Component should be rendered properly', async () => { + const loadMoreRequests = jest.fn(); render( diff --git a/src/screens/UserPortal/Campaigns/Campaigns.spec.tsx b/src/screens/UserPortal/Campaigns/Campaigns.test.tsx similarity index 88% rename from src/screens/UserPortal/Campaigns/Campaigns.spec.tsx rename to src/screens/UserPortal/Campaigns/Campaigns.test.tsx index 09cde6b25d..17b7eec4d5 100644 --- a/src/screens/UserPortal/Campaigns/Campaigns.spec.tsx +++ b/src/screens/UserPortal/Campaigns/Campaigns.test.tsx @@ -20,57 +20,39 @@ import i18nForTest from 'utils/i18nForTest'; import type { ApolloLink } from '@apollo/client'; import useLocalStorage from 'utils/useLocalstorage'; import Campaigns from './Campaigns'; -import { vi, it, expect, describe } from 'vitest'; import { EMPTY_MOCKS, MOCKS, USER_FUND_CAMPAIGNS_ERROR, } from './CampaignsMocks'; -/* Mocking 'react-toastify` */ -vi.mock('react-toastify', () => ({ +jest.mock('react-toastify', () => ({ toast: { - success: vi.fn(), - error: vi.fn(), + success: jest.fn(), + error: jest.fn(), }, })); - -/* Mocking `@mui/x-date-pickers/DateTimePicker` */ -vi.mock('@mui/x-date-pickers/DateTimePicker', async () => { - const actual = await vi.importActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ); +jest.mock('@mui/x-date-pickers/DateTimePicker', () => { return { - DateTimePicker: actual.DesktopDateTimePicker, + DateTimePicker: jest.requireActual( + '@mui/x-date-pickers/DesktopDateTimePicker', + ).DesktopDateTimePicker, }; }); - const { setItem } = useLocalStorage(); -/** - * Creates a mocked Apollo link for testing. - */ const link1 = new StaticMockLink(MOCKS); const link2 = new StaticMockLink(USER_FUND_CAMPAIGNS_ERROR); const link3 = new StaticMockLink(EMPTY_MOCKS); - const cTranslations = JSON.parse( JSON.stringify( i18nForTest.getDataByLanguage('en')?.translation.userCampaigns, ), ); - const pTranslations = JSON.parse( JSON.stringify(i18nForTest.getDataByLanguage('en')?.translation.pledges), ); -/* - * Renders the `Campaigns` component for testing. - * - * @param link - The mocked Apollo link used for testing. - * @returns The rendered result of the `Campaigns` component. - */ - const renderCampaigns = (link: ApolloLink): RenderResult => { return render( @@ -97,38 +79,26 @@ const renderCampaigns = (link: ApolloLink): RenderResult => { ); }; -/** - * Test suite for the User Campaigns screen. - */ describe('Testing User Campaigns Screen', () => { beforeEach(() => { setItem('userId', 'userId'); }); beforeAll(() => { - /** - * Mocks the `useParams` function from `react-router-dom` to simulate URL parameters. - */ - vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { - ...actual, - useParams: vi.fn(() => ({ orgId: 'orgId' })), // Mock `useParams` - }; - }); + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: 'orgId' }), + })); }); afterAll(() => { - vi.clearAllMocks(); + jest.clearAllMocks(); }); afterEach(() => { cleanup(); }); - /** - * Verifies that the User Campaigns screen renders correctly with mock data. - */ it('should render the User Campaigns screen', async () => { renderCampaigns(link1); await waitFor(() => { @@ -138,9 +108,6 @@ describe('Testing User Campaigns Screen', () => { }); }); - /** - * Ensures the app redirects to the fallback URL if `userId` is null in LocalStorage. - */ it('should redirect to fallback URL if userId is null in LocalStorage', async () => { setItem('userId', null); renderCampaigns(link1); @@ -149,11 +116,7 @@ describe('Testing User Campaigns Screen', () => { }); }); - /** - * Ensures the app redirects to the fallback URL if URL parameters are undefined. - */ it('should redirect to fallback URL if URL params are undefined', async () => { - vi.unmock('react-router-dom'); // unmocking to get real behavior from useParams render( diff --git a/src/screens/UserPortal/Donate/Donate.spec.tsx b/src/screens/UserPortal/Donate/Donate.test.tsx similarity index 93% rename from src/screens/UserPortal/Donate/Donate.spec.tsx rename to src/screens/UserPortal/Donate/Donate.test.tsx index b13056f5f9..c4d435415e 100644 --- a/src/screens/UserPortal/Donate/Donate.spec.tsx +++ b/src/screens/UserPortal/Donate/Donate.test.tsx @@ -1,14 +1,8 @@ -/** - * Unit tests for the Donate component. - * - * This file contains tests for the Donate component to ensure it behaves as expected - * under various scenarios. - */ import React, { act } from 'react'; import { render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; -import { vi } from 'vitest'; + import { ORGANIZATION_DONATION_CONNECTION_LIST, USER_ORGANIZATION_CONNECTION, @@ -138,35 +132,35 @@ async function wait(ms = 100): Promise { }); } -vi.mock('react-router-dom', async () => ({ - ...(await vi.importActual('react-router-dom')), - useParams: vi.fn(() => ({ orgId: '' })), +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: '' }), })); -vi.mock('react-toastify', () => ({ +jest.mock('react-toastify', () => ({ toast: { - error: vi.fn(), - success: vi.fn(), + error: jest.fn(), + success: jest.fn(), }, })); describe('Testing Donate Screen [User Portal]', () => { Object.defineProperty(window, 'matchMedia', { writable: true, - value: vi.fn().mockImplementation((query) => ({ + value: jest.fn().mockImplementation((query) => ({ matches: false, media: query, onchange: null, - addListener: vi.fn(), // Deprecated - removeListener: vi.fn(), // Deprecated - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), })), }); beforeEach(() => { - vi.clearAllMocks(); + jest.clearAllMocks(); }); test('Screen should be rendered properly', async () => { diff --git a/src/screens/UserPortal/UserScreen/UserScreen.spec.tsx b/src/screens/UserPortal/UserScreen/UserScreen.test.tsx similarity index 73% rename from src/screens/UserPortal/UserScreen/UserScreen.spec.tsx rename to src/screens/UserPortal/UserScreen/UserScreen.test.tsx index 65c5e6a650..642b231a66 100644 --- a/src/screens/UserPortal/UserScreen/UserScreen.spec.tsx +++ b/src/screens/UserPortal/UserScreen/UserScreen.test.tsx @@ -1,19 +1,8 @@ -/** - * This file contains unit tests for the UserScreen component. - * - * The tests cover: - * - Rendering of the correct title based on the location. - * - Functionality of the LeftDrawer component. - * - Behavior when the orgId is undefined. - * - * These tests use Vitest for test execution and MockedProvider for mocking GraphQL queries. - */ - import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import { describe, it, vi, beforeEach, expect } from 'vitest'; import { MockedProvider } from '@apollo/react-testing'; +import { fireEvent, render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; +import 'jest-location-mock'; import { Provider } from 'react-redux'; import { BrowserRouter, useNavigate } from 'react-router-dom'; import { store } from 'state/store'; @@ -21,20 +10,15 @@ import i18nForTest from 'utils/i18nForTest'; import UserScreen from './UserScreen'; import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; -import '@testing-library/jest-dom'; let mockID: string | undefined = '123'; let mockLocation: string | undefined = '/user/organization/123'; -vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { - ...actual, - useParams: () => ({ orgId: mockID }), - useLocation: () => ({ pathname: mockLocation }), - useNavigate: vi.fn(), // Mock only the necessary parts - }; -}); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: mockID }), + useLocation: () => ({ pathname: mockLocation }), +})); const MOCKS = [ { @@ -88,13 +72,8 @@ const clickToggleMenuBtn = (toggleButton: HTMLElement): void => { fireEvent.click(toggleButton); }; -describe('UserScreen tests with LeftDrawer functionality', () => { - beforeEach(() => { - mockID = '123'; - mockLocation = '/user/organization/123'; - }); - - it('renders the correct title for posts', () => { +describe('Testing LeftDrawer in OrganizationScreen', () => { + test('renders the correct title based on the titleKey for posts', () => { render( @@ -111,7 +90,7 @@ describe('UserScreen tests with LeftDrawer functionality', () => { expect(titleElement).toHaveTextContent('Posts'); }); - it('renders the correct title for people', () => { + test('renders the correct title based on the titleKey', () => { mockLocation = '/user/people/123'; render( @@ -130,7 +109,7 @@ describe('UserScreen tests with LeftDrawer functionality', () => { expect(titleElement).toHaveTextContent('People'); }); - it('toggles LeftDrawer correctly based on window size and user interaction', () => { + test('LeftDrawer should toggle correctly based on window size and user interaction', async () => { render( @@ -142,30 +121,27 @@ describe('UserScreen tests with LeftDrawer functionality', () => { , ); - const toggleButton = screen.getByTestId('closeMenu') as HTMLElement; const icon = toggleButton.querySelector('i'); - // Resize to small screen and check toggle state + // Resize window to a smaller width resizeWindow(800); clickToggleMenuBtn(toggleButton); expect(icon).toHaveClass('fa fa-angle-double-left'); - // Resize to large screen and check toggle state + // Resize window back to a larger width resizeWindow(1000); clickToggleMenuBtn(toggleButton); expect(icon).toHaveClass('fa fa-angle-double-right'); - // Check state on re-click clickToggleMenuBtn(toggleButton); expect(icon).toHaveClass('fa fa-angle-double-left'); }); - it('redirects to root when orgId is undefined', () => { + test('should be redirected to root when orgId is undefined', async () => { mockID = undefined; - const navigate = vi.fn(); - vi.spyOn({ useNavigate }, 'useNavigate').mockReturnValue(navigate); - + const navigate = jest.fn(); + jest.spyOn({ useNavigate }, 'useNavigate').mockReturnValue(navigate); render( @@ -177,7 +153,6 @@ describe('UserScreen tests with LeftDrawer functionality', () => { , ); - expect(window.location.pathname).toEqual('/'); }); }); diff --git a/src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.spec.tsx b/src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.test.tsx similarity index 90% rename from src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.spec.tsx rename to src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.test.tsx index f636d8d8d4..43e0b15cdb 100644 --- a/src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.spec.tsx +++ b/src/screens/UserPortal/Volunteer/UpcomingEvents/UpcomingEvents.test.tsx @@ -21,20 +21,11 @@ import { } from './UpcomingEvents.mocks'; import { toast } from 'react-toastify'; import useLocalStorage from 'utils/useLocalstorage'; -import { vi } from 'vitest'; -/** - * Unit tests for the UpcomingEvents component. - * - * This file contains tests to verify the functionality and behavior of the UpcomingEvents component - * under various scenarios, including successful data fetching, error handling, and user interactions. - * Mocked dependencies are used to ensure isolated testing of the component. - */ - -vi.mock('react-toastify', () => ({ +jest.mock('react-toastify', () => ({ toast: { - success: vi.fn(), - error: vi.fn(), + success: jest.fn(), + error: jest.fn(), }, })); @@ -90,13 +81,10 @@ const renderUpcomingEvents = (link: ApolloLink): RenderResult => { describe('Testing Upcoming Events Screen', () => { beforeAll(() => { - vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { - ...actual, - useParams: () => ({ orgId: 'orgId' }), - }; - }); + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: 'orgId' }), + })); }); beforeEach(() => { @@ -104,7 +92,7 @@ describe('Testing Upcoming Events Screen', () => { }); afterAll(() => { - vi.clearAllMocks(); + jest.clearAllMocks(); }); it('should redirect to fallback URL if URL params are undefined', async () => { diff --git a/src/screens/Users/Users.module.css b/src/screens/Users/Users.module.css new file mode 100644 index 0000000000..0750dba108 --- /dev/null +++ b/src/screens/Users/Users.module.css @@ -0,0 +1,95 @@ +.btnsContainer { + display: flex; + margin: 2.5rem 0 2.5rem 0; +} + +.btnsContainer .btnsBlock { + display: flex; +} + +.btnsContainer .btnsBlock button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.btnsContainer .inputContainer { + flex: 1; + position: relative; +} +.btnsContainer .input { + width: 70%; + position: relative; +} + +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainer .inputContainer button { + width: 52px; +} + +.listBox { + width: 100%; + flex: 1; +} + +.notFound { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +@media (max-width: 1020px) { + .btnsContainer { + flex-direction: column; + margin: 1.5rem 0; + } + .btnsContainer .input { + width: 100%; + } + .btnsContainer .btnsBlock { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainer .btnsBlock button { + margin: 0; + } + + .btnsContainer .btnsBlock div button { + margin-right: 1.5rem; + } +} + +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; + } + + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainer .btnsBlock div { + flex: 1; + } + + .btnsContainer .btnsBlock div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} diff --git a/src/screens/Users/Users.tsx b/src/screens/Users/Users.tsx index 3ea165d7b0..72acba5b5c 100644 --- a/src/screens/Users/Users.tsx +++ b/src/screens/Users/Users.tsx @@ -16,7 +16,7 @@ import TableLoader from 'components/TableLoader/TableLoader'; import UsersTableItem from 'components/UsersTableItem/UsersTableItem'; import InfiniteScroll from 'react-infinite-scroll-component'; import type { InterfaceQueryUserListItem } from 'utils/interfaces'; -import styles from '../../style/app.module.css'; +import styles from './Users.module.css'; import useLocalStorage from 'utils/useLocalstorage'; import type { ApolloError } from '@apollo/client'; /** @@ -91,16 +91,8 @@ const Users = (): JSX.Element => { }: { data?: { users: InterfaceQueryUserListItem[] }; loading: boolean; - fetchMore: (options: { - variables: Record; - updateQuery: ( - previousQueryResult: { users: InterfaceQueryUserListItem[] }, - options: { - fetchMoreResult?: { users: InterfaceQueryUserListItem[] }; - }, - ) => { users: InterfaceQueryUserListItem[] }; - }) => void; - refetch: (variables?: Record) => void; + fetchMore: any; + refetch: any; error?: ApolloError; } = useQuery(USER_LIST, { variables: { @@ -179,11 +171,9 @@ const Users = (): JSX.Element => { setHasMore(true); }; - const handleSearchByEnter = ( - e: React.KeyboardEvent, - ): void => { + const handleSearchByEnter = (e: any): void => { if (e.key === 'Enter') { - const { value } = e.target as HTMLInputElement; + const { value } = e.target; handleSearch(value); } }; @@ -221,16 +211,16 @@ const Users = (): JSX.Element => { { fetchMoreResult, }: { - fetchMoreResult?: { users: InterfaceQueryUserListItem[] }; + fetchMoreResult: { users: InterfaceQueryUserListItem[] } | undefined; }, - ) => { + ): { users: InterfaceQueryUserListItem[] } | undefined => { setIsLoadingMore(false); - if (!fetchMoreResult) return prev || { users: [] }; + if (!fetchMoreResult) return prev; if (fetchMoreResult.users.length < perPageResult) { setHasMore(false); } return { - users: [...(prev?.users || []), ...fetchMoreResult.users], + users: [...(prev?.users || []), ...(fetchMoreResult.users || [])], }; }, }); @@ -411,23 +401,15 @@ const Users = (): JSX.Element => { usersData && displayedUsers.length === 0 && searchByName.length > 0 ? ( -
+

{tCommon('noResultsFoundFor')} "{searchByName}"

-
+ ) : isLoading == false && usersData === undefined && displayedUsers.length === 0 ? ( -
+

{t('noUserFound')}

) : ( diff --git a/src/style/app.module.css b/src/style/app.module.css index 3bc2e1fd6c..8846246d63 100644 --- a/src/style/app.module.css +++ b/src/style/app.module.css @@ -1,7 +1,5 @@ :root { - /* Color contrast ratio: 7.5:1 (exceeds WCAG AAA) */ --high-contrast-text: #494949; - /* Color contrast ratio: 9:1 (exceeds WCAG AAA) */ --high-contrast-border: #2c2c2c; } @@ -83,6 +81,7 @@ align-items: center; gap: 10px; /* Adjust spacing between items */ + margin: 2.5rem 0; } .btnsContainer .btnsBlock { @@ -96,28 +95,15 @@ align-items: center; } -.btnsContainer .inputContainer { +.btnsContainer .input { flex: 1; position: relative; } -.btnsContainer .input { - width: 70%; -} - -.btnsContainer input { - outline: 1px solid var(--bs-gray-400); -} - -.btnsContainer .inputContainer button { +.btnsContainer .input button { width: 52px; } -.listBox { - width: 100%; - flex: 1; -} - .inputField { margin-top: 10px; margin-bottom: 10px; @@ -513,6 +499,37 @@ hr { outline-offset: -2px; } +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; + } + + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainer .btnsBlock div { + flex: 1; + } + + .btnsContainer .btnsBlock div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} + +.listBox { + width: 100%; + flex: 1; +} + .listTable { width: 100%; box-sizing: border-box; @@ -563,60 +580,185 @@ hr { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 10px; } + .cardHeader .cardTitle { font-size: 1.2rem; font-weight: 600; } -.containerBody { +.cardBody { min-height: 180px; - padding-top: 0; - max-height: 570px; - overflow-y: auto; - width: 100%; - max-width: min(400px, 90vw); - scrollbar-width: thin; - scrollbar-color: rgba(0, 0, 0, 0.3) transparent; +} - &::-webkit-scrollbar { - width: thin; - } +.cardBody .textBox { + margin: 0 0 3rem 0; + color: var(--high-contrast-text); +} - &::-webkit-scrollbar-track { - background: transparent; - } +.settingsTabs { + display: none; +} - &::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0.3); - } +.login_background { + min-height: 100vh; +} + +.communityLogo { + object-fit: contain; } -.containerBody .emptyContainer { +.row .left_portion { display: flex; - min-height: 180px; justify-content: center; align-items: center; + flex-direction: column; + height: 100vh; } -.containerBody .rankings { - aspect-ratio: 1; - border-radius: 50%; - width: 35px; +.selectOrgText input { + outline: none !important; } -.cardBody { - min-height: 180px; +.row .left_portion .inner .palisadoes_logo { + width: 600px; + height: auto; } -.cardBody .textBox { - margin: 0 0 3rem 0; - color: var(--high-contrast-text); +.row .right_portion { + min-height: 100vh; + position: relative; + overflow-y: scroll; + display: flex; + flex-direction: column; + justify-content: center; + padding: 1rem 2.5rem; + background: var(--bs-white); } -.settingsTabs { - display: none; +.row .right_portion::-webkit-scrollbar { + width: 8px; +} + +.row .right_portion::-webkit-scrollbar-track { + background: transparent; +} + +.row .right_portion::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +.row .right_portion .langChangeBtn { + margin: 0; + position: absolute; + top: 1rem; + left: 1rem; +} + +.langChangeBtnStyle { + width: 7.5rem; + height: 2.2rem; + padding: 0; +} + +.row .right_portion .talawa_logo { + height: 5rem; + width: 5rem; + display: block; + margin: 1.5rem auto 1rem; + -webkit-animation: zoomIn 0.3s ease-in-out; + animation: zoomIn 0.3s ease-in-out; +} + +.row .orText { + display: block; + position: absolute; + top: 0; + left: calc(50% - 2.6rem); + margin: 0 auto; + padding: 0.35rem 2rem; + z-index: 100; + background: var(--bs-white); + color: var(--bs-secondary); +} + +.email_button { + position: absolute; + z-index: 10; + bottom: 0; + right: 0; + height: 100%; + display: flex; + background-color: var(--search-button-bg); + border-color: var(--search-button-border); + justify-content: center; + align-items: center; +} + +.login_btn { + background-color: var(--search-button-bg); + border-color: var(--search-button-border); + margin-top: 1rem; + /* mt-3: Bootstrap margin spacing utility (3 = 1rem) */ + margin-bottom: 1rem; + /* mb-3: Bootstrap margin spacing utility (3 = 1rem) */ + width: 100%; + transition: background-color 0.2s ease; +} + +.reg_btn { + background-color: var(--dropdown-border-color); + border-color: var(--dropdown-border-color); + margin-top: 1rem; + color: white; + /* mt-3: Bootstrap margin spacing utility (3 = 1rem) */ + margin-bottom: 1rem; + /* mb-3: Bootstrap margin spacing utility (3 = 1rem) */ + width: 100%; + transition: background-color 0.2s ease; +} + +.login_btn:focus, +.reg_btn:focus { + outline: 2px solid var(--search-button-bg); + outline-offset: 2px; +} + +.login_btn:active, +.reg_btn:active { + transform: translateY(1px); +} + +.active_tab { + -webkit-animation: fadeIn 0.3s ease-in-out; + animation: fadeIn 0.3s ease-in-out; +} + +.socialIcons { + display: flex; + gap: 16px; + justify-content: center; +} + +.password_checks { + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-direction: column; + gap: 0.5rem; +} + +.password_check_element { + margin-top: 0; +} + +.password_check_element_top { + margin-top: 1rem; +} + +.password_check_element_bottom { + margin-bottom: 0; } @media (min-width: 576px) { @@ -631,6 +773,20 @@ hr { } } +@media (max-width: 1120px) { + .contract { + padding-left: calc(250px + 2rem + 1.5rem); + } + + .listBox .itemCard { + width: 100%; + } + + .row .left_portion .inner .palisadoes_logo { + width: 100%; + } +} + @media (max-width: 1020px) { .btnsContainer { flex-direction: column; @@ -655,6 +811,78 @@ hr { } } +@media (max-width: 992px) { + .row .left_portion { + padding: 0 2rem; + } +} + +@media (max-width: 769px) { + .row { + flex-direction: column-reverse; + } + + .row .right_portion, + .row .left_portion { + height: unset; + } + + .row .right_portion { + min-height: 100vh; + overflow-y: unset; + } + + .row .left_portion .inner { + display: flex; + justify-content: center; + } + + .row .left_portion .inner .palisadoes_logo { + height: 70px; + width: unset; + position: absolute; + margin: 0.5rem; + top: 0; + right: 0; + z-index: 100; + } + + .row .left_portion .inner p { + margin-bottom: 0; + padding: 1rem; + } + + .socialIcons { + margin-bottom: 1rem; + } +} + +@media (max-width: 577px) { + .row .right_portion { + padding: 1rem 1rem 0 1rem; + } + + .row .right_portion .langChangeBtn { + position: absolute; + margin: 1rem; + left: 0; + top: 0; + } + + .marginTopForReg { + margin-top: 4rem !important; + } + + .row .right_portion .talawa_logo { + height: 120px; + margin: 0 auto 2rem auto; + } + + .socialIcons { + margin-bottom: 1rem; + } +} + @media (max-width: 520px) { .btnsContainer { margin-bottom: 0; @@ -680,13 +908,69 @@ hr { } } -@media (max-width: 1120px) { - .contract { - padding-left: calc(250px + 2rem + 1.5rem); +@media (prefers-reduced-motion: reduce) { + .talawa_logo { + animation: none; } - .listBox .itemCard { - width: 100%; + .active_tab { + animation: none; + } +} + +@-webkit-keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@-webkit-keyframes fadeIn { + 0% { + opacity: 0; + -webkit-transform: translateY(2rem); + transform: translateY(2rem); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + -webkit-transform: translateY(2rem); + transform: translateY(2rem); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); } } diff --git a/vitest.config.ts b/vitest.config.ts index c158cf9c2a..ad85276111 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; import tsconfigPaths from 'vite-tsconfig-paths'; -import svgrPlugin from 'vite-plugin-svgr'; export default defineConfig({ plugins: [ @@ -11,7 +10,6 @@ export default defineConfig({ include: ['events'], }), tsconfigPaths(), - svgrPlugin(), ], test: { include: ['src/**/*.spec.{js,jsx,ts,tsx}'],