From 78be925a71621e040945ede0e1e7e6b03eb9861e Mon Sep 17 00:00:00 2001 From: Vivek Bisen <121126140+vivekbisen04@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:26:08 +0530 Subject: [PATCH 01/34] Improve Code Coverage in src/screens/OrgSettings/OrgSetting.tsx (#3091) * Improved code coverage OrgSetting.tsx --- src/screens/OrgSettings/OrgSettings.spec.tsx | 44 +++++++++++++++++--- src/screens/OrgSettings/OrgSettings.tsx | 6 +-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/screens/OrgSettings/OrgSettings.spec.tsx b/src/screens/OrgSettings/OrgSettings.spec.tsx index 5b5179d644..a98fface20 100644 --- a/src/screens/OrgSettings/OrgSettings.spec.tsx +++ b/src/screens/OrgSettings/OrgSettings.spec.tsx @@ -130,18 +130,50 @@ describe('Organisation Settings Page', () => { }); }); - it('should render dropdown for settings tabs', async () => { + it('should handle dropdown item selection correctly', async () => { renderOrganisationSettings(); await waitFor(() => { - expect(screen.getByTestId('settingsDropdownToggle')).toBeInTheDocument(); + expect( + screen.getByTestId('settingsDropdownContainer'), + ).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('settingsDropdownToggle')); + const dropdownToggle = screen.getByTestId('settingsDropdownToggle'); + userEvent.click(dropdownToggle); - const dropdownItems = screen.getAllByRole('button', { - name: /general|actionItemCategories|agendaItemCategories/i, - }); + // Find all dropdown items + const dropdownItems = screen.getAllByRole('menuitem'); expect(dropdownItems).toHaveLength(3); + + for (const item of dropdownItems) { + userEvent.click(item); + + if (item.textContent?.includes('general')) { + await waitFor(() => { + expect(screen.getByTestId('generalTab')).toBeInTheDocument(); + }); + } else if (item.textContent?.includes('actionItemCategories')) { + await waitFor(() => { + expect( + screen.getByTestId('actionItemCategoriesTab'), + ).toBeInTheDocument(); + }); + } else if (item.textContent?.includes('agendaItemCategories')) { + await waitFor(() => { + expect( + screen.getByTestId('agendaItemCategoriesTab'), + ).toBeInTheDocument(); + }); + } + + if (item !== dropdownItems[dropdownItems.length - 1]) { + userEvent.click(dropdownToggle); + } + } + + expect(dropdownToggle).toHaveTextContent( + screen.getByTestId('agendaItemCategoriesSettings').textContent || '', + ); }); }); diff --git a/src/screens/OrgSettings/OrgSettings.tsx b/src/screens/OrgSettings/OrgSettings.tsx index c7b01138ae..641bc27c7d 100644 --- a/src/screens/OrgSettings/OrgSettings.tsx +++ b/src/screens/OrgSettings/OrgSettings.tsx @@ -81,10 +81,8 @@ function OrgSettings(): JSX.Element { {settingtabs.map((setting, index) => ( setTab(setting) - } + role="menuitem" + onClick={() => setTab(setting)} className={tab === setting ? 'text-secondary' : ''} > {t(setting)} From b60c4762146d72d11f1a7fea5a0852d32c61296d Mon Sep 17 00:00:00 2001 From: Dhiren-Mhatre <130587526+Dhiren-Mhatre@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:09:57 +0530 Subject: [PATCH 02/34] =?UTF-8?q?improved=20code=20coverage=20of=20src/scr?= =?UTF-8?q?eens/OrganizationVenues/Organization=E2=80=A6=20(#3121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * improved code coverage of src/screens/OrganizationVenues/OrganizationVenues.tsx * fixed formatting --- .../OrganizationVenues.spec.tsx | 111 ++++++++++++++++++ .../OrganizationVenues/OrganizationVenues.tsx | 2 - 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/screens/OrganizationVenues/OrganizationVenues.spec.tsx b/src/screens/OrganizationVenues/OrganizationVenues.spec.tsx index e306c56cfc..0f257b3710 100644 --- a/src/screens/OrganizationVenues/OrganizationVenues.spec.tsx +++ b/src/screens/OrganizationVenues/OrganizationVenues.spec.tsx @@ -31,6 +31,8 @@ import { VENUE_LIST } from 'GraphQl/Queries/OrganizationQueries'; import type { ApolloLink } from '@apollo/client'; import { DELETE_VENUE_MUTATION } from 'GraphQl/Mutations/VenueMutations'; import { vi } from 'vitest'; +import { errorHandler } from 'utils/errorHandler'; + const MOCKS = [ { request: { @@ -505,3 +507,112 @@ describe('Organisation Venues', () => { }); }); }); + +vi.mock('utils/errorHandler'); +describe('Organisation Venues Error Handling', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + test('handles venue query error correctly', async () => { + const mockError = new Error('Failed to fetch venues'); + const errorLink = new StaticMockLink([ + { + request: { + query: VENUE_LIST, + variables: { + orgId: 'orgId', + orderBy: 'capacity_DESC', + where: { + name_starts_with: '', + description_starts_with: undefined, + }, + }, + }, + error: mockError, + }, + ]); + + renderOrganizationVenue(errorLink); + + await waitFor(() => { + expect(errorHandler).toHaveBeenCalledWith( + expect.any(Function), + mockError, + ); + }); + }); + + test('handles venue deletion error correctly', async () => { + const mockError = new Error('Failed to delete venue'); + const errorLink = new StaticMockLink( + [ + { + request: { + query: VENUE_LIST, + variables: { + orgId: 'orgId', + orderBy: 'capacity_DESC', + where: { + name_starts_with: '', + description_starts_with: undefined, + }, + }, + }, + result: { + data: { + getVenueByOrgId: [ + { + _id: 'venue1', + name: 'Test Venue', + description: 'Test Description', + capacity: 100, + // ... other required fields + }, + ], + }, + }, + }, + { + request: { + query: DELETE_VENUE_MUTATION, + variables: { id: 'venue1' }, + }, + error: mockError, + }, + ], + true, + ); + + renderOrganizationVenue(errorLink); + + await waitFor(() => { + expect(screen.getByTestId('deleteVenueBtn1')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByTestId('deleteVenueBtn1')); + + await waitFor(() => { + expect(errorHandler).toHaveBeenCalledWith( + expect.any(Function), + mockError, + ); + }); + }); + + test('renders venue list correctly after loading', async () => { + renderOrganizationVenue(link); + + // First verify loading state + expect(screen.getByTestId('spinner-wrapper')).toBeInTheDocument(); + + // Then verify venues are rendered + await waitFor(() => { + const venueList = screen.getByTestId('orgvenueslist'); + expect(venueList).toBeInTheDocument(); + + const venues = screen.getAllByTestId(/^venue-item/); + expect(venues).toHaveLength(3); + }); + }); +}); diff --git a/src/screens/OrganizationVenues/OrganizationVenues.tsx b/src/screens/OrganizationVenues/OrganizationVenues.tsx index b3afda9685..3914ed5748 100644 --- a/src/screens/OrganizationVenues/OrganizationVenues.tsx +++ b/src/screens/OrganizationVenues/OrganizationVenues.tsx @@ -78,7 +78,6 @@ function organizationVenues(): JSX.Element { }); venueRefetch(); } catch (error) { - /* istanbul ignore next */ errorHandler(t, error); } }; @@ -128,7 +127,6 @@ function organizationVenues(): JSX.Element { }; // Error handling for venue data fetch - /* istanbul ignore next */ if (venueError) { errorHandler(t, venueError); } From 5ceb6ff7727d652b7396240f8138ee212ae6e32a Mon Sep 17 00:00:00 2001 From: Peter Harrison <16875803+palisadoes@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:58:30 -0800 Subject: [PATCH 03/34] Update validate-coderabbit.sh --- .github/workflows/scripts/validate-coderabbit.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scripts/validate-coderabbit.sh b/.github/workflows/scripts/validate-coderabbit.sh index d4f6aec019..a549035581 100644 --- a/.github/workflows/scripts/validate-coderabbit.sh +++ b/.github/workflows/scripts/validate-coderabbit.sh @@ -33,5 +33,6 @@ else echo " statement below to restart a review" echo "" echo "@coderabbitai full review" + echo "" exit 1 fi From 32f51ead7f44a89eaed1d26b227aefdd5f992791 Mon Sep 17 00:00:00 2001 From: Ramneet Singh <144323012+Ramneet04@users.noreply.github.com> Date: Fri, 3 Jan 2025 08:03:36 +0530 Subject: [PATCH 04/34] Improve Code Coverage in src/screens/UserPortal/Volunteer/VolunteerManagement.tsx #3044 (#3125) * name changed * code coverage success * prettier fix --- ....test.tsx => VolunteerManagement.spec.tsx} | 35 ++++++++++++++++--- .../Volunteer/VolunteerManagement.tsx | 5 +-- 2 files changed, 31 insertions(+), 9 deletions(-) rename src/screens/UserPortal/Volunteer/{VolunteerManagement.test.tsx => VolunteerManagement.spec.tsx} (78%) diff --git a/src/screens/UserPortal/Volunteer/VolunteerManagement.test.tsx b/src/screens/UserPortal/Volunteer/VolunteerManagement.spec.tsx similarity index 78% rename from src/screens/UserPortal/Volunteer/VolunteerManagement.test.tsx rename to src/screens/UserPortal/Volunteer/VolunteerManagement.spec.tsx index 80ac0df833..6fb9c218f9 100644 --- a/src/screens/UserPortal/Volunteer/VolunteerManagement.test.tsx +++ b/src/screens/UserPortal/Volunteer/VolunteerManagement.spec.tsx @@ -13,6 +13,7 @@ import userEvent from '@testing-library/user-event'; import { MOCKS } from './UpcomingEvents/UpcomingEvents.mocks'; import { StaticMockLink } from 'utils/StaticMockLink'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; const { setItem } = useLocalStorage(); const link1 = new StaticMockLink(MOCKS); @@ -43,10 +44,13 @@ const renderVolunteerManagement = (): RenderResult => { describe('Volunteer Management', () => { beforeAll(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId' }), - })); + vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); // Import the actual implementation + return { + ...actual, + useParams: () => ({ orgId: 'orgId' }), + }; + }); }); beforeEach(() => { @@ -54,10 +58,11 @@ describe('Volunteer Management', () => { }); afterAll(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should redirect to fallback URL if URL params are undefined', async () => { + setItem('userId', null); render( @@ -126,4 +131,24 @@ describe('Volunteer Management', () => { const groupsTab = screen.getByTestId('groupsTab'); expect(groupsTab).toBeInTheDocument(); }); + test('Component should highlight the selected tab', async () => { + renderVolunteerManagement(); + + const upcomingEventsBtn = screen.getByTestId('upcomingEventsBtn'); + const invitationsBtn = screen.getByTestId('invitationsBtn'); + // Click the invitations tab + userEvent.click(invitationsBtn); + await waitFor(() => { + expect(invitationsBtn).toHaveClass('btn-success'); + expect(upcomingEventsBtn).not.toHaveClass('btn-success'); + }); + }); + test('should update the component state on tab switch', async () => { + renderVolunteerManagement(); + + const actionsBtn = screen.getByTestId('actionsBtn'); + userEvent.click(actionsBtn); + const actionsTab = screen.getByTestId('actionsTab'); + expect(actionsTab).toBeInTheDocument(); + }); }); diff --git a/src/screens/UserPortal/Volunteer/VolunteerManagement.tsx b/src/screens/UserPortal/Volunteer/VolunteerManagement.tsx index 87be9d7adc..fd90584d62 100644 --- a/src/screens/UserPortal/Volunteer/VolunteerManagement.tsx +++ b/src/screens/UserPortal/Volunteer/VolunteerManagement.tsx @@ -151,10 +151,7 @@ const VolunteerManagement = (): JSX.Element => { {volunteerDashboardTabs.map(({ value, icon }, index) => ( setTab(value) - } + onClick={() => setTab(value)} className={`d-flex gap-2 ${tab === value && 'text-secondary'}`} > {icon} {t(value)} From bb7e4d73208ba495dac83e22bddc989a744132de Mon Sep 17 00:00:00 2001 From: Peter Harrison <16875803+palisadoes@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:59:47 -0800 Subject: [PATCH 05/34] Update pull-request.yml --- .github/workflows/pull-request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 2a1aae5f92..7d28eeef40 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -97,6 +97,7 @@ jobs: with: files: | .env* + vitest.config.js src/App.tsx .github/** env.example From 2215c0a5b55da6a1e207c02c2245cb2c80c228e4 Mon Sep 17 00:00:00 2001 From: Aadhil Ahamed Date: Fri, 3 Jan 2025 10:46:12 +0530 Subject: [PATCH 06/34] Improved Code Coverage in src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx (#3127) --- .../OrganizationNavbar.spec.tsx | 95 +++++++++++++++++++ .../OrganizationNavbar/OrganizationNavbar.tsx | 8 +- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.spec.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.spec.tsx index 7f9e8e52bb..d0296b1221 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.spec.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.spec.tsx @@ -35,6 +35,9 @@ import { vi } from 'vitest'; * 9. **Plugin removal on uninstallation**: Ensures plugins are removed when uninstalled for the organization. * 10. **Rendering plugins when not uninstalled**: Ensures plugins render if not uninstalled. * 11. **No changes for unmatched plugin**: Ensures no changes when an unrecognized plugin update occurs. + * 12. **Should handle logout properly**: Ensures that local storage is cleared and user is redirected to home screen when logout button is clicked + * 13. **Should navigate to home page on home link click**: Ensures that browser history is correctly updated and navigates to oraganization home page. + * 14. **Should use fallback "en" when cookies.get returns null: Ensures component fallsback to "en" language when i18next cookie is absent. * * Mocked GraphQL queries and subscriptions simulate backend behavior. */ @@ -473,4 +476,96 @@ describe('Testing OrganizationNavbar Component [User Portal]', () => { await wait(); }); + + it('Should handle logout properly', async () => { + const mockStorage = { + clear: vi.fn(), + getItem: vi.fn((key: 'name' | 'talawaPlugins') => { + const items = { + name: JSON.stringify('Test User'), + talawaPlugins: JSON.stringify([]), + }; + return items[key] || null; + }), + setItem: vi.fn(), + removeItem: vi.fn(), + length: 0, + key: vi.fn(), + }; + Object.defineProperty(window, 'localStorage', { + value: mockStorage, + }); + const mockLocation = { + replace: vi.fn(), + }; + Object.defineProperty(window, 'location', { + value: mockLocation, + writable: true, + }); + render( + + + + + + + + + , + ); + await wait(); + userEvent.click(screen.getByTestId('personIcon')); + userEvent.click(screen.getByTestId('logoutBtn')); + expect(mockStorage.clear).toHaveBeenCalled(); + expect(mockLocation.replace).toHaveBeenCalledWith('/'); + }); + + it('Should navigate to home page on home link click', async () => { + const history = createMemoryHistory({ + initialEntries: ['/initial'], + }); + render( + + + + + + + + + , + ); + const homeLink = screen.getByText('Home'); + expect(homeLink).toBeInTheDocument(); + userEvent.click(homeLink); + await wait(); + expect(history.location.pathname).toBe( + `/user/organization/${organizationId}`, + ); + }); +}); + +describe('Testing OrganizationNavbar Cookie Fallback', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should use fallback "en" when cookies.get returns null', async () => { + vi.spyOn(cookies, 'get').mockReturnValue( + null as unknown as { [key: string]: string }, + ); + render( + + + + + + + + + , + ); + expect(cookies.get).toHaveBeenCalledWith('i18next'); + expect(screen.getByText('Home')).toBeInTheDocument(); + }); }); diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx index 34022fcfcf..6cfe3078eb 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx @@ -62,7 +62,6 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { }); const [currentLanguageCode, setCurrentLanguageCode] = React.useState( - /* istanbul ignore next */ cookies.get('i18next') || 'en', ); @@ -71,7 +70,6 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { /** * Handles user logout by clearing local storage and redirecting to the home page. */ - /* istanbul ignore next */ const handleLogout = (): void => { localStorage.clear(); window.location.replace('/'); @@ -142,6 +140,7 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { console.log(`Plugin ${pluginName} is not present.`); } } else { + /* istanbul ignore else -- @preserve */ if (pluginIndexToRemove != -1) { plugins[pluginIndexToRemove].view = true; setItem('talawaPlugins', JSON.stringify(plugins)); @@ -174,10 +173,7 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element {