diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 75588cc4df..c423143e6f 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 + ./.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/screens/ManageTag/ManageTag.tsx - name: Get changed TypeScript files id: changed-files diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 2485fec3e4..7d2108bc06 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -345,14 +345,21 @@ "remove": "Remove", "successfullyAssignedToTags": "Successfully Assigned to Tags", "successfullyRemovedFromTags": "Successfully Removed from Tags", - "errorOccurredWhileLoadingOrganizationUserTags": "Error occured while loading organization tags", + "errorOccurredWhileLoadingOrganizationUserTags": "Error occurred while loading organization tags", + "errorOccurredWhileLoadingSubTags": "Error occurred while loading subTags tags", "removeUserTag": "Delete Tag", "removeUserTagMessage": "Do you want to delete this tag? It delete all the sub tags and all the associations.", "tagDetails": "Tag Details", "tagName": "Name", "tagUpdationSuccess": "Tag updated successfully", "tagRemovalSuccess": "Tag deleted successfully", - "noTagSelected": "No Tag Selected" + "noTagSelected": "No Tag Selected", + "changeNameToEdit": "Change the name to make an update", + "selectTag": "Select Tag", + "collapse": "Collapse", + "expand": "Expand", + "tagNamePlaceholder": "Write the name of the tag", + "allTags": "All Tags" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 9957849399..7f74826272 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -330,29 +330,36 @@ "unassignUserTag": "Désassigner l'étiquette", "unassignUserTagMessage": "Voulez-vous retirer l'étiquette de cet utilisateur?", "successfullyUnassigned": "Étiquette retirée de l'utilisateur", + "addPeople": "Ajouter des personnes", "add": "Ajouter", "subTags": "Sous-étiquettes", "assignedToAll": "Étiquette attribuée à tous", "successfullyAssignedToPeople": "Étiquette attribuée avec succès", - "assignPeople": "Attribuer", "errorOccurredWhileLoadingMembers": "Erreur survenue lors du chargement des membres", "userName": "Nom d'utilisateur", "actions": "Actions", "noOneSelected": "Personne sélectionnée", - "assignToTags": "Attribuer aux tags", - "removeFromTags": "Retirer des tags", + "assignToTags": "Attribuer aux étiquettes", + "removeFromTags": "Retirer des étiquettes", "assign": "Attribuer", "remove": "Retirer", - "successfullyAssignedToTags": "Attribué aux tags avec succès", - "successfullyRemovedFromTags": "Retiré des tags avec succès", - "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des tags de l'organisation", - "removeUserTag": "Supprimer le tag", - "removeUserTagMessage": "Voulez-vous supprimer ce tag ? Cela supprimera tous les sous-tags et toutes les associations.", - "tagDetails": "Détails du tag", - "tagName": "Nom", + "successfullyAssignedToTags": "Attribué aux étiquettes avec succès", + "successfullyRemovedFromTags": "Retiré des étiquettes avec succès", + "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des étiquettes de l'organisation", + "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-étiquettes", + "removeUserTag": "Supprimer l'étiquette", + "removeUserTagMessage": "Voulez-vous supprimer cette étiquette ? Cela supprimera toutes les sous-étiquettes et toutes les associations.", + "tagDetails": "Détails de l'étiquette", + "tagName": "Nom de l'étiquette", "tagUpdationSuccess": "Étiquette mise à jour avec succès", "tagRemovalSuccess": "Étiquette supprimée avec succès", - "noTagSelected": "Aucun tag sélectionné" + "noTagSelected": "Aucune étiquette sélectionnée", + "changeNameToEdit": "Modifiez le nom pour faire une mise à jour", + "selectTag": "Sélectionner l'étiquette", + "collapse": "Réduire", + "expand": "Développer", + "tagNamePlaceholder": "Écrire le nom de l'étiquette", + "allTags": "Toutes les étiquettes" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index cdc1d5e52b..4384648ca3 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -346,13 +346,20 @@ "successfullyAssignedToTags": "सफलतापूर्वक टैग्स को असाइन किया गया", "successfullyRemovedFromTags": "सफलतापूर्वक टैग्स से हटाया गया", "errorOccurredWhileLoadingOrganizationUserTags": "संगठन टैग्स को लोड करते समय त्रुटि हुई", + "errorOccurredWhileLoadingSubTags": "उप-टैग लोड करते समय त्रुटि हुई", "removeUserTag": "टैग हटाएं", "removeUserTagMessage": "क्या आप इस टैग को हटाना चाहते हैं? यह सभी उप-टैग्स और सभी संबंधों को हटा देगा।", "tagDetails": "टैग विवरण", "tagName": "नाम", "tagUpdationSuccess": "टैग सफलतापूर्वक अपडेट की गई", "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई", - "noTagSelected": "कोई टैग चयनित नहीं" + "noTagSelected": "कोई टैग चयनित नहीं", + "changeNameToEdit": "अपडेट करने के लिए नाम बदलें", + "selectTag": "टैग चुनें", + "collapse": "संक्षिप्त करें", + "expand": "विस्तारित करें", + "tagNamePlaceholder": "टैग का नाम लिखें", + "allTags": "सभी टैग" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index fc6d9eb839..24ce0dbdec 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -346,13 +346,20 @@ "successfullyAssignedToTags": "Asignado a etiquetas con éxito", "successfullyRemovedFromTags": "Eliminado de etiquetas con éxito", "errorOccurredWhileLoadingOrganizationUserTags": "Error al cargar las etiquetas de la organización", + "errorOccurredWhileLoadingSubTags": "Ocurrió un error al cargar las subetiquetas", "removeUserTag": "Eliminar etiqueta", "removeUserTagMessage": "¿Desea eliminar esta etiqueta? Esto eliminará todas las subetiquetas y todas las asociaciones.", "tagDetails": "Detalles de la etiqueta", "tagName": "Nombre", "tagUpdationSuccess": "Etiqueta actualizada con éxito", "tagRemovalSuccess": "Etiqueta eliminada con éxito", - "noTagSelected": "Ninguna etiqueta seleccionada" + "noTagSelected": "Ninguna etiqueta seleccionada", + "changeNameToEdit": "Cambia el nombre para hacer una actualización", + "selectTag": "Seleccionar etiqueta", + "collapse": "Colapsar", + "expand": "Expandir", + "tagNamePlaceholder": "Escribe el nombre de la etiqueta", + "allTags": "Todas las etiquetas" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index a264056547..0c070bcf8b 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -346,13 +346,20 @@ "successfullyAssignedToTags": "成功分配到标签", "successfullyRemovedFromTags": "成功从标签中移除", "errorOccurredWhileLoadingOrganizationUserTags": "加载组织标签时出错", + "errorOccurredWhileLoadingSubTags": "加载子标签时发生错误", "removeUserTag": "删除标签", "removeUserTagMessage": "您要删除此标签吗?这将删除所有子标签和所有关联。", "tagDetails": "标签详情", "tagName": "名称", "tagUpdationSuccess": "标签更新成功", "tagRemovalSuccess": "标签删除成功", - "noTagSelected": "未选择标签" + "noTagSelected": "未选择标签", + "changeNameToEdit": "更改名称以进行更新", + "selectTag": "选择标签", + "collapse": "收起", + "expand": "展开", + "tagNamePlaceholder": "输入标签名称", + "allTags": "所有标签" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 4014dcfc89..65792c010d 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -7,6 +7,7 @@ import { fireEvent, cleanup, waitFor, + act, } from '@testing-library/react'; import { Provider } from 'react-redux'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; @@ -21,15 +22,21 @@ import type { ApolloLink } from '@apollo/client'; import type { InterfaceTagActionsProps } from './TagActions'; import TagActions from './TagActions'; import i18n from 'utils/i18nForTest'; -import { MOCKS, MOCKS_ERROR } from './TagActionsMocks'; +import { + MOCKS, + MOCKS_ERROR_ORGANIZATION_TAGS_QUERY, + MOCKS_ERROR_SUBTAGS_QUERY, +} from './TagActionsMocks'; const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(MOCKS_ERROR, true); +const link2 = new StaticMockLink(MOCKS_ERROR_ORGANIZATION_TAGS_QUERY, true); +const link3 = new StaticMockLink(MOCKS_ERROR_SUBTAGS_QUERY, true); -async function wait(): Promise { - await waitFor(() => { - // The waitFor utility automatically uses optimal timing - return Promise.resolve(); +async function wait(ms = 500): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); }); } @@ -155,6 +162,24 @@ describe('Organisation Tags Page', () => { }); }); + test('Renders error component when when subTags query is unsuccessful', async () => { + const { getByText } = renderTagActionsModal(props[0], link3); + + await wait(); + + // expand tag 1 to list its subtags + await waitFor(() => { + expect(screen.getByTestId('expandSubTags1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('expandSubTags1')); + + await waitFor(() => { + expect( + getByText(translations.errorOccurredWhileLoadingSubTags), + ).toBeInTheDocument(); + }); + }); + test('Renders more members with infinite scroll', async () => { const { getByText } = renderTagActionsModal(props[0], link); diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index 756c656ba8..ece511e6ec 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -101,12 +101,12 @@ const TagActions: React.FC = ({ }); }; - const userTagsList = orgUserTagsData?.organizations[0].userTags.edges.map( + const userTagsList = orgUserTagsData?.organizations[0]?.userTags.edges.map( (edge) => edge.node, ); const [checkedTagId, setCheckedTagId] = useState(null); - const [uncheckedTagId, setUnheckedTagId] = useState(null); + const [uncheckedTagId, setUncheckedTagId] = useState(null); // tags that we have selected to assigned const [selectedTags, setSelectedTags] = useState([]); @@ -167,11 +167,11 @@ const TagActions: React.FC = ({ const addAncestorTags = (tagId: string): void => { setCheckedTagId(tagId); - setUnheckedTagId(null); + setUncheckedTagId(null); }; const removeAncestorTags = (tagId: string): void => { - setUnheckedTagId(tagId); + setUncheckedTagId(tagId); setCheckedTagId(null); }; @@ -228,49 +228,32 @@ const TagActions: React.FC = ({ }); const [assignToTags] = useMutation(ASSIGN_TO_TAGS); - - const assignToSelectedTags = async ( - e: FormEvent, - ): Promise => { - e.preventDefault(); - - try { - const { data } = await assignToTags({ - variables: { - currentTagId, - selectedTagIds: selectedTags.map((selectedTag) => selectedTag._id), - }, - }); - - if (data) { - toast.success(t('successfullyAssignedToTags')); - hideTagActionsModal(); - } - } catch (error: unknown) { - /* istanbul ignore next */ - if (error instanceof Error) { - toast.error(error.message); - } - } - }; - const [removeFromTags] = useMutation(REMOVE_FROM_TAGS); - const removeFromSelectedTags = async ( + const handleTagAction = async ( e: FormEvent, ): Promise => { e.preventDefault(); + const mutationObject = { + variables: { + currentTagId, + selectedTagIds: selectedTags.map((selectedTag) => selectedTag._id), + }, + }; + try { - const { data } = await removeFromTags({ - variables: { - currentTagId, - selectedTagIds: selectedTags.map((selectedTag) => selectedTag._id), - }, - }); + const { data } = + tagActionType === 'assignToTags' + ? await assignToTags(mutationObject) + : await removeFromTags(mutationObject); if (data) { - toast.success(t('successfullyRemovedFromTags')); + if (tagActionType === 'assignToTags') { + toast.success(t('successfullyAssignedToTags')); + } else { + toast.success(t('successfullyRemovedFromTags')); + } hideTagActionsModal(); } } catch (error: unknown) { @@ -288,8 +271,6 @@ const TagActions: React.FC = ({
{t('errorOccurredWhileLoadingOrganizationUserTags')} -
- {orgUserTagsError.message}
@@ -316,13 +297,7 @@ const TagActions: React.FC = ({ : t('removeFromTags')} -
- tagActionType === 'assignToTags' - ? assignToSelectedTags(e) - : removeFromSelectedTags(e) - } - > + {orgUserTagsLoading ? ( @@ -342,10 +317,11 @@ const TagActions: React.FC = ({ className={`badge bg-dark-subtle text-secondary-emphasis lh-lg my-2 ms-2 d-flex align-items-center ${styles.tagBadge}`} > {tag.name} - deSelectTag(tag)} data-testid={`clearSelectedTag${tag._id}`} + aria-label={t('remove')} /> )) @@ -353,7 +329,7 @@ const TagActions: React.FC = ({
- All Tags + {t('allTags')}
; @@ -18,6 +22,9 @@ interface InterfaceTagNodeProps { t: (key: string) => string; } +/** + * Renders the Tags which can be expanded to list subtags. + */ const TagNode: React.FC = ({ tag, checkedTags, @@ -29,6 +36,7 @@ const TagNode: React.FC = ({ const { data: subTagsData, loading: subTagsLoading, + error: subTagsError, fetchMore: fetchMoreSubTags, }: InterfaceOrganizationSubTagsQuery = useQuery(USER_TAG_SUB_TAGS, { variables: { @@ -70,16 +78,25 @@ const TagNode: React.FC = ({ }); }; + if (subTagsError) { + return ( +
+
+ +
+ {t('errorOccurredWhileLoadingSubTags')} +
+
+
+ ); + } + const subTagsList = subTagsData?.getChildTags.childTags.edges.map( (edge) => edge.node, ); const handleTagClick = (): void => { - if (!expanded) { - setExpanded(true); - } else { - setExpanded(false); // collapse on second click - } + setExpanded(!expanded); }; const handleCheckboxChange = ( @@ -98,6 +115,7 @@ const TagNode: React.FC = ({ className="me-3" style={{ cursor: 'pointer' }} data-testid={`expandSubTags${tag._id}`} + aria-label={expanded ? t('collapse') : t('expand')} > {expanded ? '▼' : '▶'} @@ -108,6 +126,7 @@ const TagNode: React.FC = ({ className="me-2" onChange={handleCheckboxChange} data-testid={`checkTag${tag._id}`} + aria-label={t('selectTag')} /> {' '} @@ -121,6 +140,7 @@ const TagNode: React.FC = ({ className="ms-1 me-2" onChange={handleCheckboxChange} data-testid={`checkTag${tag._id}`} + aria-label={tag.name} /> {' '} @@ -136,7 +156,7 @@ const TagNode: React.FC = ({
)} - {expanded && subTagsList && ( + {expanded && subTagsList?.length && (
{ jest.mock('react-toastify', () => ({ toast: { success: jest.fn(), + info: jest.fn(), error: jest.fn(), }, })); @@ -398,6 +399,12 @@ describe('Manage Tag Page', () => { }); userEvent.click(screen.getByTestId('editTag')); + userEvent.click(screen.getByTestId('editTagSubmitBtn')); + + await waitFor(() => { + expect(toast.info).toHaveBeenCalledWith(translations.changeNameToEdit); + }); + const tagNameInput = screen.getByTestId('tagNameInput'); await userEvent.clear(tagNameInput); await userEvent.type(tagNameInput, 'tag 1 edited'); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index b04e838c64..f94bbfc72d 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -91,6 +91,10 @@ function ManageTag(): JSX.Element { setTagActionsModalIsOpen(false); }; + const showEditTagModal = (): void => { + setEditTagModalIsOpen(true); + }; + const hideEditTagModal = (): void => { setEditTagModalIsOpen(false); }; @@ -190,6 +194,8 @@ function ManageTag(): JSX.Element { const [edit] = useMutation(UPDATE_USER_TAG); const [newTagName, setNewTagName] = useState(''); + const currentTagName = + userTagAssignedMembersData?.getAssignedUsers.name ?? ''; useEffect(() => { setNewTagName(userTagAssignedMembersData?.getAssignedUsers.name ?? ''); @@ -198,6 +204,11 @@ function ManageTag(): JSX.Element { const editTag = async (e: FormEvent): Promise => { e.preventDefault(); + if (newTagName === currentTagName) { + toast.info(t('changeNameToEdit')); + return; + } + try { const { data } = await edit({ variables: { @@ -527,18 +538,14 @@ function ManageTag(): JSX.Element {
{ - setEditTagModalIsOpen(true); - }} + onClick={showEditTagModal} className="ms-5 mt-3 mb-2 btn btn-primary btn-sm w-75" data-testid="editTag" > {tCommon('edit')}
{ - setRemoveTagModalIsOpen(true); - }} + onClick={toggleRemoveUserTagModal} className="ms-5 mb-2 btn btn-danger btn-sm w-75" data-testid="removeTag" > @@ -624,8 +631,8 @@ function ManageTag(): JSX.Element { {t('tagName')}