diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 48efc8efcc..aaeebc8345 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -38,7 +38,7 @@ jobs: - name: Count number of lines run: | chmod +x ./.github/workflows/countline.py - ./.github/workflows/countline.py --lines 600 --exclude_files src/screens/LoginPage/LoginPage.tsx src/GraphQl/Queries/Queries.ts src/screens/OrgList/OrgList.tsx src/GraphQl/Mutations/mutations.ts src/components/EventListCard/EventListCardModals.tsx src/components/TagActions/TagActionsMocks.ts src/utils/interfaces.ts + ./.github/workflows/countline.py --lines 600 --exclude_files src/screens/LoginPage/LoginPage.tsx src/GraphQl/Queries/Queries.ts src/screens/OrgList/OrgList.tsx src/GraphQl/Mutations/mutations.ts src/components/EventListCard/EventListCardModals.tsx src/components/TagActions/TagActionsMocks.ts src/utils/interfaces.ts src/screens/MemberDetail/MemberDetail.tsx - name: Get changed TypeScript files id: changed-files diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 080013e2d1..5da5ef49ee 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -987,7 +987,9 @@ "talawaApiUnavailable": "talawaApiUnavailable", "unassignUserTag": "Unassign Tag", "unassignUserTagMessage": "Do you want to remove the tag from this user?", - "successfullyUnassigned": "Tag unassigned from user" + "successfullyUnassigned": "Tag unassigned from user", + "tagsAssigned": "Tags Assigned", + "noTagsAssigned": "No Tags Assigned" }, "people": { "title": "People", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 595807db3d..8226b51f05 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -958,7 +958,9 @@ "talawaApiUnavailable": "API Talawa non disponible", "unassignUserTag": "Désassigner l'étiquette", "unassignUserTagMessage": "Voulez-vous retirer l'étiquette de cet utilisateur?", - "successfullyUnassigned": "Étiquette retirée de l'utilisateur" + "successfullyUnassigned": "Étiquette retirée de l'utilisateur", + "tagsAssigned": "étiquettes assignés", + "noTagsAssigned": "Aucun étiquette assigné" }, "people": { "title": "Personnes", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 6e229cf89d..2645340b23 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -958,7 +958,9 @@ "talawaApiUnavailable": "Talawa API अनुपलब्ध", "unassignUserTag": "टैग को हटाएं", "unassignUserTagMessage": "क्या आप इस उपयोगकर्ता से टैग हटाना चाहते हैं?", - "successfullyUnassigned": "उपयोगकर्ता से टैग हटा दिया गया" + "successfullyUnassigned": "उपयोगकर्ता से टैग हटा दिया गया", + "tagsAssigned": "टैग सौंपे गए", + "noTagsAssigned": "कोई टैग सौंपा नहीं गया" }, "people": { "title": "लोग", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index d45c72fe4f..7aa1d6ffc0 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -959,7 +959,9 @@ "userType": "Tipo de usuario", "unassignUserTag": "Desasignar Etiqueta", "unassignUserTagMessage": "¿Desea eliminar la etiqueta de este usuario?", - "successfullyUnassigned": "Etiqueta desasignada del usuario" + "successfullyUnassigned": "Etiqueta desasignada del usuario", + "tagsAssigned": "Etiquetas asignadas", + "noTagsAssigned": "No se asignaron etiquetas" }, "userLogin": { "login": "Acceso", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index da92a5dbf5..adafbdbe45 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -958,7 +958,9 @@ "talawaApiUnavailable": "塔拉瓦 API 不可用", "unassignUserTag": "取消分配标签", "unassignUserTagMessage": "您想从此用户中删除标签吗?", - "successfullyUnassigned": "标签已从用户中取消分配" + "successfullyUnassigned": "标签已从用户中取消分配", + "tagsAssigned": "已分配标签", + "noTagsAssigned": "未分配标签" }, "userLogin": { "login": "登录", diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index fa6f72589c..81442cbad2 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -558,10 +558,6 @@ export const USER_DETAILS = gql` parentTag { _id } - ancestorTags { - _id - name - } } } pageInfo { diff --git a/src/screens/MemberDetail/MemberDetail.test.tsx b/src/screens/MemberDetail/MemberDetail.test.tsx index 2d7a5cfaca..b0514701f5 100644 --- a/src/screens/MemberDetail/MemberDetail.test.tsx +++ b/src/screens/MemberDetail/MemberDetail.test.tsx @@ -1,14 +1,17 @@ import React from 'react'; +import type { RenderResult } from '@testing-library/react'; import { act, + cleanup, fireEvent, render, screen, waitFor, + waitForElementToBeRemoved, } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; -import { BrowserRouter } from 'react-router-dom'; +import { BrowserRouter, MemoryRouter, Route, Routes } from 'react-router-dom'; import { Provider } from 'react-redux'; import { store } from 'state/store'; import { I18nextProvider } from 'react-i18next'; @@ -16,28 +19,30 @@ import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import MemberDetail, { getLanguageName, prettyDate } from './MemberDetail'; import { MOCKS1, MOCKS2, MOCKS3 } from './MemberDetailMocks'; +import type { ApolloLink } from '@apollo/client'; +import { toast } from 'react-toastify'; const link1 = new StaticMockLink(MOCKS1, true); const link2 = new StaticMockLink(MOCKS2, true); const link3 = new StaticMockLink(MOCKS3, true); -async function wait(ms = 20): Promise { +async function wait(ms = 500): Promise { await act(() => new Promise((resolve) => setTimeout(resolve, ms))); } -// const translations = { -// ...JSON.parse( -// JSON.stringify( -// i18nForTest.getDataByLanguage('en')?.translation.memberDetail ?? {}, -// ), -// ), -// ...JSON.parse( -// JSON.stringify(i18nForTest.getDataByLanguage('en')?.common ?? {}), -// ), -// ...JSON.parse( -// JSON.stringify(i18nForTest.getDataByLanguage('en')?.errors ?? {}), -// ), -// }; +const translations = { + ...JSON.parse( + JSON.stringify( + i18nForTest.getDataByLanguage('en')?.translation.memberDetail ?? {}, + ), + ), + ...JSON.parse( + JSON.stringify(i18nForTest.getDataByLanguage('en')?.common ?? {}), + ), + ...JSON.parse( + JSON.stringify(i18nForTest.getDataByLanguage('en')?.errors ?? {}), + ), +}; jest.mock('@mui/x-date-pickers/DateTimePicker', () => { return { @@ -47,30 +52,54 @@ jest.mock('@mui/x-date-pickers/DateTimePicker', () => { }; }); -jest.mock('react-toastify'); +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const props = { + id: 'rishav-jha-mech', +}; + +const renderMemberDetailScreen = (link: ApolloLink): RenderResult => { + return render( + + + + + + } + /> + } + /> + + + + + , + ); +}; describe('MemberDetail', () => { global.alert = jest.fn(); + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + test('should render the elements', async () => { - const props = { - id: 'rishav-jha-mech', - }; + renderMemberDetailScreen(link1); - render( - - - - - - - - - , - ); + await wait(); expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); - await wait(); expect(screen.getAllByText(/Email/i)).toBeTruthy(); expect(screen.getAllByText(/First name/i)).toBeTruthy(); expect(screen.getAllByText(/Last name/i)).toBeTruthy(); @@ -103,10 +132,6 @@ describe('MemberDetail', () => { }); test('should render props and text elements test for the page component', async () => { - const props = { - id: '1', - }; - const formData = { firstName: 'Ansh', lastName: 'Goyal', @@ -119,19 +144,11 @@ describe('MemberDetail', () => { phoneNumber: '1234567890', birthDate: '03/28/2022', }; - render( - - - - - - - - - , - ); - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + renderMemberDetailScreen(link2); + await wait(); + + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); expect(screen.getAllByText(/Email/i)).toBeTruthy(); expect(screen.getByText('User')).toBeInTheDocument(); const birthDateDatePicker = screen.getByTestId('birthDate'); @@ -139,23 +156,39 @@ describe('MemberDetail', () => { target: { value: formData.birthDate }, }); + userEvent.clear(screen.getByPlaceholderText(/First Name/i)); userEvent.type( screen.getByPlaceholderText(/First Name/i), formData.firstName, ); + + userEvent.clear(screen.getByPlaceholderText(/Last Name/i)); userEvent.type( screen.getByPlaceholderText(/Last Name/i), formData.lastName, ); + + userEvent.clear(screen.getByPlaceholderText(/Address/i)); userEvent.type(screen.getByPlaceholderText(/Address/i), formData.address); + + userEvent.clear(screen.getByPlaceholderText(/Country Code/i)); userEvent.type( screen.getByPlaceholderText(/Country Code/i), formData.countryCode, ); + + userEvent.clear(screen.getByPlaceholderText(/State/i)); userEvent.type(screen.getByPlaceholderText(/State/i), formData.state); + + userEvent.clear(screen.getByPlaceholderText(/City/i)); userEvent.type(screen.getByPlaceholderText(/City/i), formData.city); + + userEvent.clear(screen.getByPlaceholderText(/Email/i)); userEvent.type(screen.getByPlaceholderText(/Email/i), formData.email); + + userEvent.clear(screen.getByPlaceholderText(/Phone/i)); userEvent.type(screen.getByPlaceholderText(/Phone/i), formData.phoneNumber); + // userEvent.click(screen.getByPlaceholderText(/pluginCreationAllowed/i)); // userEvent.selectOptions(screen.getByTestId('applangcode'), 'Français'); // userEvent.upload(screen.getByLabelText(/Display Image:/i), formData.image); @@ -178,65 +211,22 @@ describe('MemberDetail', () => { }); test('display admin', async () => { - const props = { - id: 'rishav-jha-mech', - }; - - render( - - - - - - - - - , - ); - - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + renderMemberDetailScreen(link1); await wait(); + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); expect(screen.getByText('Admin')).toBeInTheDocument(); }); - test('display super admin', async () => { - const props = { - id: 'rishav-jha-mech', - }; - - render( - - - - - - - - - , - ); - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + test('display super admin', async () => { + renderMemberDetailScreen(link3); await wait(); + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); expect(screen.getByText('Super Admin')).toBeInTheDocument(); }); test('Should display dicebear image if image is null', async () => { - const props = { - id: 'rishav-jha-mech', - from: 'orglist', - }; + renderMemberDetailScreen(link1); - render( - - - - - - - - - , - ); expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); const dicebearUrl = `mocked-data-uri`; @@ -247,22 +237,7 @@ describe('MemberDetail', () => { }); test('Should display image if image is present', async () => { - const props = { - id: 'rishav-jha-mech', - from: 'orglist', - }; - - render( - - - - - - - - - , - ); + renderMemberDetailScreen(link2); expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); @@ -271,22 +246,9 @@ describe('MemberDetail', () => { expect(userImage).toBeInTheDocument(); expect(userImage.getAttribute('src')).toBe(user?.image); }); + test('resetChangesBtn works properly', async () => { - const props = { - id: 'rishav-jha-mech', - from: 'orglist', - }; - render( - - - - - - - - - , - ); + renderMemberDetailScreen(link1); await waitFor(() => { expect(screen.getByPlaceholderText(/Address/i)).toBeInTheDocument(); @@ -306,27 +268,6 @@ describe('MemberDetail', () => { expect(screen.getByTestId('birthDate')).toHaveValue('03/14/2024'); }); - test('should call setState with 2 when button is clicked', async () => { - const props = { - id: 'rishav-jha-mech', - }; - render( - - - - - - - - - , - ); - - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); - - waitFor(() => userEvent.click(screen.getByText(/Edit Profile/i))); - }); - test('should be redirected to / if member id is undefined', async () => { render( @@ -341,43 +282,18 @@ describe('MemberDetail', () => { ); expect(window.location.pathname).toEqual('/'); }); + test('renders events attended card correctly and show a message', async () => { - const props = { - id: 'rishav-jha-mech', - }; - render( - - - - - - - - - , - ); + renderMemberDetailScreen(link3); await waitFor(() => { expect(screen.getByText('Events Attended')).toBeInTheDocument(); }); // Check for empty state immediately expect(screen.getByText('No Events Attended')).toBeInTheDocument(); }); - test('opens "Events Attended List" modal when View All button is clicked', async () => { - const props = { - id: 'rishav-jha-mech', - }; - render( - - - - - - - - - , - ); + test('opens "Events Attended List" modal when View All button is clicked', async () => { + renderMemberDetailScreen(link2); await wait(); @@ -389,4 +305,96 @@ describe('MemberDetail', () => { const modalTitle = await screen.findByText('Events Attended List'); expect(modalTitle).toBeInTheDocument(); }); + + test('lists all the tags assigned to the user', async () => { + renderMemberDetailScreen(link1); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('tagName')).toHaveLength(10); + }); + }); + + test('navigates to manage tag screen after clicking manage tag option', async () => { + renderMemberDetailScreen(link1); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('tagName')[0]); + + await waitFor(() => { + expect(screen.getByTestId('manageTagScreen')).toBeInTheDocument(); + }); + }); + + test('loads more assigned tags with infinite scroll', async () => { + renderMemberDetailScreen(link1); + + await wait(); + + // now scroll to the bottom of the div + const tagsAssignedScrollableDiv = screen.getByTestId( + 'tagsAssignedScrollableDiv', + ); + + // Get the initial number of tags loaded + const initialTagsAssignedLength = screen.getAllByTestId('tagName').length; + + // Set scroll position to the bottom + fireEvent.scroll(tagsAssignedScrollableDiv, { + target: { scrollY: tagsAssignedScrollableDiv.scrollHeight }, + }); + + await waitFor(() => { + const finalTagsAssignedLength = screen.getAllByTestId('tagName').length; + expect(finalTagsAssignedLength).toBeGreaterThan( + initialTagsAssignedLength, + ); + }); + }); + + test('opens and closes the unassign tag modal', async () => { + renderMemberDetailScreen(link1); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('unassignTagBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('unassignTagBtn')[0]); + + await waitFor(() => { + return expect( + screen.findByTestId('unassignTagModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('unassignTagModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('unassignTagModalCloseBtn'), + ); + }); + + test('unassigns a tag from a member', async () => { + renderMemberDetailScreen(link1); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('unassignTagBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('unassignTagBtn')[0]); + + userEvent.click(screen.getByTestId('unassignTagModalSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toHaveBeenCalledWith( + translations.successfullyUnassigned, + ); + }); + }); }); diff --git a/src/screens/MemberDetail/MemberDetail.tsx b/src/screens/MemberDetail/MemberDetail.tsx index 62ab7f3341..cd552c80a0 100644 --- a/src/screens/MemberDetail/MemberDetail.tsx +++ b/src/screens/MemberDetail/MemberDetail.tsx @@ -615,8 +615,7 @@ const MemberDetail: React.FC = ({ id }): JSX.Element => { className={`bg-primary d-flex justify-content-between align-items-center py-3 px-4 ${styles.topRadius}`} >

- {/* {t('eventsAttended')} */} - Tags Assigned + {t('tagsAssigned')}

= ({ id }): JSX.Element => { > {!tagsAssigned.length ? (
- {/*
{t('noeventsAttended')}
*/} - No Tags Assigned + {t('noTagsAssigned')}
) : (