From 12793a167aa89213250e9c595c05510200cdb730 Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 1 Nov 2024 21:03:34 +0530 Subject: [PATCH] add tests --- .../AddPeopleToTag/AddPeopleToTag.module.css | 3 +- .../AddPeopleToTag/AddPeopleToTag.test.tsx | 99 ++++++++++- .../AddPeopleToTag/AddPeopleToTag.tsx | 10 +- .../AddPeopleToTag/AddPeopleToTagsMocks.ts | 106 ++++++++++++ .../TagActions/TagActions.module.css | 10 +- src/components/TagActions/TagActions.test.tsx | 21 +++ src/components/TagActions/TagActions.tsx | 123 +++++++++----- src/components/TagActions/TagActionsMocks.ts | 77 +++++++++ src/components/TagActions/TagNode.tsx | 2 +- src/screens/ManageTag/ManageTag.test.tsx | 78 +++++++++ src/screens/ManageTag/ManageTag.tsx | 3 +- src/screens/ManageTag/ManageTagMocks.ts | 113 +++++++++++++ .../OrganizationTags.test.tsx | 82 +++++++++- .../OrganizationTags/OrganizationTags.tsx | 38 +++-- .../OrganizationTags/OrganizationTagsMocks.ts | 154 ++++++++++++++++++ src/screens/SubTags/SubTags.test.tsx | 110 ++++++++++++- src/screens/SubTags/SubTags.tsx | 5 +- src/screens/SubTags/SubTagsMocks.ts | 144 ++++++++++++++++ 18 files changed, 1100 insertions(+), 78 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css index d4f24626ee9..5dd04ffed58 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.module.css +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -28,8 +28,7 @@ } .scrollContainer { - min-height: 100px; - max-height: 100px; + height: 100px; overflow-y: auto; margin-bottom: 1rem; } diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx index ef9977eb2f0..99bdbd67e31 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -17,7 +17,7 @@ import { store } from 'state/store'; import userEvent from '@testing-library/user-event'; import { StaticMockLink } from 'utils/StaticMockLink'; import { toast } from 'react-toastify'; -import type { ApolloLink } from '@apollo/client'; +import { InMemoryCache, type ApolloLink } from '@apollo/client'; import type { InterfaceAddPeopleToTagProps } from './AddPeopleToTag'; import AddPeopleToTag from './AddPeopleToTag'; import i18n from 'utils/i18nForTest'; @@ -63,12 +63,39 @@ const props: InterfaceAddPeopleToTagProps = { >, }; +const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + getUserTag: { + merge(existing = {}, incoming) { + const merged = { + ...existing, + ...incoming, + usersToAssignTo: { + ...existing.usersToAssignTo, + ...incoming.usersToAssignTo, + edges: [ + ...(existing.usersToAssignTo?.edges || []), + ...(incoming.usersToAssignTo?.edges || []), + ], + }, + }; + + return merged; + }, + }, + }, + }, + }, +}); + const renderAddPeopleToTagModal = ( props: InterfaceAddPeopleToTagProps, link: ApolloLink, ): RenderResult => { return render( - + @@ -91,7 +118,7 @@ describe('Organisation Tags Page', () => { ...jest.requireActual('react-router-dom'), useParams: () => ({ orgId: 'orgId' }), })); - // cache.reset(); + cache.reset(); }); afterEach(() => { @@ -147,6 +174,72 @@ describe('Organisation Tags Page', () => { userEvent.click(screen.getAllByTestId('deselectMemberBtn')[0]); }); + test('searchs for tags where the firstName matches the provided firstName search input', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.firstName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.firstName); + fireEvent.change(input, { target: { value: 'usersToAssignTo' } }); + + // should render the two users from the mock data + // where firstName starts with "usersToAssignTo" + await waitFor(() => { + const members = screen.getAllByTestId('memberName'); + expect(members.length).toEqual(2); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'usersToAssignTo user1', + ); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[1]).toHaveTextContent( + 'usersToAssignTo user2', + ); + }); + }); + + test('searchs for tags where the lastName matches the provided lastName search input', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.lastName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.lastName); + fireEvent.change(input, { target: { value: 'userToAssignTo' } }); + + // should render the two users from the mock data + // where lastName starts with "usersToAssignTo" + await waitFor(() => { + const members = screen.getAllByTestId('memberName'); + expect(members.length).toEqual(2); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'first userToAssignTo', + ); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[1]).toHaveTextContent( + 'second userToAssignTo', + ); + }); + }); + test('Renders more members with infinite scroll', async () => { const { getByText } = renderAddPeopleToTagModal(props, link); diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index e8dcd279afb..ec0d779f7af 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -99,7 +99,7 @@ const AddPeopleToTag: React.FC = ({ }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { getUsersToAssignTo: { @@ -263,7 +263,7 @@ const AddPeopleToTag: React.FC = ({ className={`d-flex flex-wrap align-items-center border border-2 border-dark-subtle bg-light-subtle rounded-3 p-2 ${styles.scrollContainer}`} > {assignToMembers.length === 0 ? ( -
+
{t('noOneSelected')}
) : ( @@ -294,9 +294,8 @@ const AddPeopleToTag: React.FC = ({ onChange={(e) => setMemberToAssignToSearchFirstName(e.target.value.trim()) } - data-testid="searchByName" + data-testid="searchByFirstName" autoComplete="off" - required />
@@ -309,9 +308,8 @@ const AddPeopleToTag: React.FC = ({ onChange={(e) => setMemberToAssignToSearchLastName(e.target.value.trim()) } - data-testid="searchByName" + data-testid="searchByLastName" autoComplete="off" - required />
diff --git a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts index 223fcd3064d..fbaf8121864 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts +++ b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts @@ -9,6 +9,10 @@ export const MOCKS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, }, }, result: { @@ -117,6 +121,10 @@ export const MOCKS = [ id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, after: '10', + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, }, }, result: { @@ -154,6 +162,100 @@ export const MOCKS = [ }, }, }, + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: 'usersToAssignTo' }, + lastName: { starts_with: '' }, + }, + }, + }, + result: { + data: { + getUsersToAssignTo: { + name: 'tag1', + usersToAssignTo: { + edges: [ + { + node: { + _id: '1', + firstName: 'usersToAssignTo', + lastName: 'user1', + }, + cursor: '1', + }, + { + node: { + _id: '2', + firstName: 'usersToAssignTo', + lastName: 'user2', + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + }, + }, + }, + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: 'userToAssignTo' }, + }, + }, + }, + result: { + data: { + getUsersToAssignTo: { + name: 'tag1', + usersToAssignTo: { + edges: [ + { + node: { + _id: '1', + firstName: 'first', + lastName: 'userToAssignTo', + }, + cursor: '1', + }, + { + node: { + _id: '2', + firstName: 'second', + lastName: 'userToAssignTo', + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + }, + }, + }, { request: { query: ADD_PEOPLE_TO_TAG, @@ -179,6 +281,10 @@ export const MOCKS_ERROR = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/components/TagActions/TagActions.module.css b/src/components/TagActions/TagActions.module.css index 62c58559818..079dffea65a 100644 --- a/src/components/TagActions/TagActions.module.css +++ b/src/components/TagActions/TagActions.module.css @@ -17,9 +17,8 @@ } .scrollContainer { - max-height: 100px; + height: 100px; overflow-y: auto; - margin-bottom: 1rem; } .tagBadge { @@ -34,3 +33,10 @@ .removeFilterIcon { cursor: pointer; } + +.loadingDiv { + min-height: 300px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 140504d0447..da6e6528d98 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -199,6 +199,27 @@ describe('Organisation Tags Page', () => { }); }); + test('searchs for tags where the name matches the provided search input', async () => { + renderTagActionsModal(props[0], link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchUserTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + const tags = screen.getAllByTestId('orgUserTag'); + expect(tags.length).toEqual(2); + }); + }); + 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 aec6ccc4947..1c954c4b0d1 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -1,5 +1,4 @@ import { useMutation, useQuery } from '@apollo/client'; -import Loader from 'components/Loader/Loader'; import type { FormEvent } from 'react'; import React, { useEffect, useState } from 'react'; import { Modal, Form, Button } from 'react-bootstrap'; @@ -51,6 +50,8 @@ const TagActions: React.FC = ({ }) => { const { orgId, tagId: currentTagId } = useParams(); + const [tagSearchName, setTagSearchName] = useState(''); + const { data: orgUserTagsData, loading: orgUserTagsLoading, @@ -60,6 +61,7 @@ const TagActions: React.FC = ({ variables: { id: orgId, first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: tagSearchName } }, }, skip: !tagActionsModalIsOpen, }); @@ -80,7 +82,7 @@ const TagActions: React.FC = ({ }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { organizations: [ @@ -275,44 +277,58 @@ const TagActions: React.FC = ({
+
+ {selectedTags.length === 0 ? ( +
+ {t('noTagSelected')} +
+ ) : ( + selectedTags.map((tag: InterfaceTagData) => ( +
+ {tag.name} +
+ )) + )} +
+ +
+ + setTagSearchName(e.target.value.trim())} + data-testid="searchByName" + autoComplete="off" + /> +
+ +
+ {t('allTags')} +
{orgUserTagsLoading ? ( - +
+ +
) : ( <> -
- {selectedTags.length === 0 ? ( -
- {t('noTagSelected')} -
- ) : ( - selectedTags.map((tag: InterfaceTagData) => ( -
- {tag.name} -
- )) - )} -
- -
- {t('allTags')} -
-
@@ -327,17 +343,36 @@ const TagActions: React.FC = ({ scrollableTarget="scrollableDiv" > {userTagsList?.map((tag) => ( -
- +
+
+ +
+ + {/* Ancestor tags breadcrumbs positioned at the end of TagNode */} + {tag.parentTag && ( +
+ <>{'('} + {tag.ancestorTags?.map((ancestorTag) => ( + + {ancestorTag.name} + + + ))} + <>{')'} +
+ )}
))} diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index 20ae3e53490..f22458fa533 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -13,6 +13,7 @@ export const MOCKS = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, }, }, result: { @@ -192,6 +193,7 @@ export const MOCKS = [ id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, after: '10', + where: { name: { starts_with: '' } }, }, }, result: { @@ -244,6 +246,79 @@ export const MOCKS = [ }, }, }, + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchUserTag' } }, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: '1', + name: 'searchUserTag 1', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: '1', + }, + { + node: { + _id: '2', + name: 'searchUserTag 2', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + ], + }, + }, + }, { request: { query: USER_TAG_SUB_TAGS, @@ -551,6 +626,7 @@ export const MOCKS_ERROR_ORGANIZATION_TAGS_QUERY = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, }, }, error: new Error('Mock Graphql Error for organization root tags query'), @@ -564,6 +640,7 @@ export const MOCKS_ERROR_SUBTAGS_QUERY = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, }, }, result: { diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index e05d572f307..4a085ecaf9c 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -61,7 +61,7 @@ const TagNode: React.FC = ({ fetchMoreResult?: { getChildTags: InterfaceQueryUserTagChildTags }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { getChildTags: { diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index c660e441dbe..598a15cc9aa 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -324,6 +324,84 @@ describe('Manage Tag Page', () => { }); }); + test('searchs for tags where the name matches the provided search input', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'assigned user' } }); + + // should render the two users from the mock data + // where firstName starts with "assigned" and lastName starts with "user" + await waitFor(() => { + const buttons = screen.getAllByTestId('viewProfileBtn'); + expect(buttons.length).toEqual(2); + }); + }); + + test('fetches the tags by the sort order, i.e. latest or oldest first', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'assigned user' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'assigned user1', + ); + }); + + // now change the sorting order + await waitFor(() => { + expect(screen.getByTestId('sortPeople')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortPeople')); + + await waitFor(() => { + expect(screen.getByTestId('oldest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('oldest')); + + // returns the tags in reverse order + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'assigned user2', + ); + }); + + await waitFor(() => { + expect(screen.getByTestId('sortPeople')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortPeople')); + + await waitFor(() => { + expect(screen.getByTestId('latest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('latest')); + + // reverse the order again + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'assigned user1', + ); + }); + }); + test('Fetches more assigned members with infinite scroll', async () => { const { getByText } = renderManageTag(link); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 2c6892d4f80..39e97e2b762 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -142,7 +142,7 @@ function ManageTag(): JSX.Element { }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { getAssignedUsers: { @@ -378,7 +378,6 @@ function ManageTag(): JSX.Element { } data-testid="searchByName" autoComplete="off" - required />
diff --git a/src/screens/ManageTag/ManageTagMocks.ts b/src/screens/ManageTag/ManageTagMocks.ts index b7ef4cef464..5ce1e625951 100644 --- a/src/screens/ManageTag/ManageTagMocks.ts +++ b/src/screens/ManageTag/ManageTagMocks.ts @@ -13,6 +13,11 @@ export const MOCKS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -122,6 +127,11 @@ export const MOCKS = [ id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, after: '10', + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -160,6 +170,104 @@ export const MOCKS = [ }, }, }, + { + request: { + query: USER_TAGS_ASSIGNED_MEMBERS, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: 'assigned' }, + lastName: { starts_with: 'user' }, + }, + sortedBy: { id: 'DESCENDING' }, + }, + }, + result: { + data: { + getAssignedUsers: { + name: 'tag1', + usersAssignedTo: { + edges: [ + { + node: { + _id: '1', + firstName: 'assigned', + lastName: 'user1', + }, + cursor: '1', + }, + { + node: { + _id: '2', + firstName: 'assigned', + lastName: 'user2', + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + ancestorTags: [], + }, + }, + }, + }, + { + request: { + query: USER_TAGS_ASSIGNED_MEMBERS, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: 'assigned' }, + lastName: { starts_with: 'user' }, + }, + sortedBy: { id: 'ASCENDING' }, + }, + }, + result: { + data: { + getAssignedUsers: { + name: 'tag1', + usersAssignedTo: { + edges: [ + { + node: { + _id: '2', + firstName: 'assigned', + lastName: 'user2', + }, + cursor: '2', + }, + { + node: { + _id: '1', + firstName: 'assigned', + lastName: 'user1', + }, + cursor: '1', + }, + ], + pageInfo: { + startCursor: '2', + endCursor: '1', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + ancestorTags: [], + }, + }, + }, + }, { request: { query: UNASSIGN_USER_TAG, @@ -216,6 +324,11 @@ export const MOCKS_ERROR_ASSIGNED_MEMBERS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, + sortedBy: { id: 'DESCENDING' }, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/screens/OrganizationTags/OrganizationTags.test.tsx b/src/screens/OrganizationTags/OrganizationTags.test.tsx index 1ab94ce35c8..723eef3b88f 100644 --- a/src/screens/OrganizationTags/OrganizationTags.test.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.test.tsx @@ -88,7 +88,7 @@ describe('Organisation Tags Page', () => { cleanup(); }); - test('Component loads correctly', async () => { + test('component loads correctly', async () => { const { getByText } = renderOrganizationTags(link); await wait(); @@ -160,7 +160,85 @@ describe('Organisation Tags Page', () => { }); }); - test('Fetches more tags with infinite scroll', async () => { + test('searchs for tags where the name matches the provided search input', async () => { + renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchUserTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + const buttons = screen.getAllByTestId('manageTagBtn'); + expect(buttons.length).toEqual(2); + }); + }); + + test('fetches the tags by the sort order, i.e. latest or oldest first', async () => { + renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchUserTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchUserTag 1', + ); + }); + + // now change the sorting order + await waitFor(() => { + expect(screen.getByTestId('sortTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortTags')); + + await waitFor(() => { + expect(screen.getByTestId('oldest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('oldest')); + + // returns the tags in reverse order + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchUserTag 2', + ); + }); + + await waitFor(() => { + expect(screen.getByTestId('sortTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortTags')); + + await waitFor(() => { + expect(screen.getByTestId('latest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('latest')); + + // reverse the order again + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchUserTag 1', + ); + }); + }); + + test('fetches more tags with infinite scroll', async () => { const { getByText } = renderOrganizationTags(link); await wait(); diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index d239bac7a32..27e8cff9c86 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -13,7 +13,10 @@ import Modal from 'react-bootstrap/Modal'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; -import type { InterfaceQueryOrganizationUserTags } from 'utils/interfaces'; +import type { + InterfaceQueryOrganizationUserTags, + InterfaceTagData, +} from 'utils/interfaces'; import styles from './OrganizationTags.module.css'; import { DataGrid } from '@mui/x-data-grid'; import type { @@ -84,6 +87,7 @@ function OrganizationTags(): JSX.Element { first: TAGS_QUERY_DATA_CHUNK_SIZE, after: orgUserTagsData?.organizations?.[0]?.userTags?.pageInfo?.endCursor ?? + /* istanbul ignore next */ null, }, updateQuery: ( @@ -96,7 +100,7 @@ function OrganizationTags(): JSX.Element { }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { organizations: [ @@ -192,16 +196,29 @@ function OrganizationTags(): JSX.Element { minWidth: 100, sortable: false, headerClassName: `${styles.tableHeader}`, - renderCell: (params: GridCellParams) => { + renderCell: (params: GridCellParams) => { return ( -
redirectToSubTags(params.row._id)} - > - {params.row.name} +
+ {params.row.parentTag && + params.row.ancestorTags?.map((tag) => ( +
+ {tag.name} + +
+ ))} - +
redirectToSubTags(params.row._id)} + > + {params.row.name} + +
); }, @@ -285,7 +302,6 @@ function OrganizationTags(): JSX.Element { data-testid="searchByName" onChange={(e) => setTagSearchName(e.target.value.trim())} autoComplete="off" - required />
diff --git a/src/screens/OrganizationTags/OrganizationTagsMocks.ts b/src/screens/OrganizationTags/OrganizationTagsMocks.ts index c3c54c138ae..0fe48ca97f6 100644 --- a/src/screens/OrganizationTags/OrganizationTagsMocks.ts +++ b/src/screens/OrganizationTags/OrganizationTagsMocks.ts @@ -9,6 +9,8 @@ export const MOCKS = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -188,6 +190,8 @@ export const MOCKS = [ id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, after: '10', + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -240,6 +244,154 @@ export const MOCKS = [ }, }, }, + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchUserTag' } }, + sortedBy: { id: 'DESCENDING' }, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: 'searchUserTag1', + name: 'searchUserTag 1', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchUserTag1', + }, + { + node: { + _id: 'searchUserTag2', + name: 'searchUserTag 2', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchUserTag2', + }, + ], + pageInfo: { + startCursor: 'searchUserTag1', + endCursor: 'searchUserTag2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + ], + }, + }, + }, + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchUserTag' } }, + sortedBy: { id: 'ASCENDING' }, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: 'searchUserTag2', + name: 'searchUserTag 2', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchUserTag2', + }, + { + node: { + _id: 'searchUserTag1', + name: 'searchUserTag 1', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchUserTag1', + }, + ], + pageInfo: { + startCursor: 'searchUserTag2', + endCursor: 'searchUserTag1', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + ], + }, + }, + }, { request: { query: CREATE_USER_TAG, @@ -265,6 +417,8 @@ export const MOCKS_ERROR = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/screens/SubTags/SubTags.test.tsx b/src/screens/SubTags/SubTags.test.tsx index 6445d259e81..145d31109da 100644 --- a/src/screens/SubTags/SubTags.test.tsx +++ b/src/screens/SubTags/SubTags.test.tsx @@ -21,7 +21,7 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import i18n from 'utils/i18nForTest'; import SubTags from './SubTags'; import { MOCKS, MOCKS_ERROR_SUB_TAGS } from './SubTagsMocks'; -import { type ApolloLink } from '@apollo/client'; +import { InMemoryCache, type ApolloLink } from '@apollo/client'; const translations = { ...JSON.parse( @@ -51,9 +51,36 @@ jest.mock('react-toastify', () => ({ }, })); +const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + getUserTag: { + merge(existing = {}, incoming) { + const merged = { + ...existing, + ...incoming, + childTags: { + ...existing.childTags, + ...incoming.childTags, + edges: [ + ...(existing.childTags?.edges || []), + ...(incoming.childTags?.edges || []), + ], + }, + }; + + return merged; + }, + }, + }, + }, + }, +}); + const renderSubTags = (link: ApolloLink): RenderResult => { return render( - + @@ -84,6 +111,7 @@ describe('Organisation Tags Page', () => { ...jest.requireActual('react-router-dom'), useParams: () => ({ orgId: 'orgId' }), })); + cache.reset(); }); afterEach(() => { @@ -208,6 +236,84 @@ describe('Organisation Tags Page', () => { }); }); + test('searchs for tags where the name matches the provided search input', async () => { + renderSubTags(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchSubTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + const buttons = screen.getAllByTestId('manageTagBtn'); + expect(buttons.length).toEqual(2); + }); + }); + + test('fetches the tags by the sort order, i.e. latest or oldest first', async () => { + renderSubTags(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchSubTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchSubTag 1', + ); + }); + + // now change the sorting order + await waitFor(() => { + expect(screen.getByTestId('sortTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortTags')); + + await waitFor(() => { + expect(screen.getByTestId('oldest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('oldest')); + + // returns the tags in reverse order + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchSubTag 2', + ); + }); + + await waitFor(() => { + expect(screen.getByTestId('sortTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortTags')); + + await waitFor(() => { + expect(screen.getByTestId('latest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('latest')); + + // reverse the order again + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchSubTag 1', + ); + }); + }); + test('Fetches more sub tags with infinite scroll', async () => { const { getByText } = renderSubTags(link); diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index 7d49c690471..95aca87c5bc 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -93,7 +93,7 @@ function SubTags(): JSX.Element { fetchMoreResult?: { getChildTags: InterfaceQueryUserTagChildTags }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { getChildTags: { @@ -291,7 +291,6 @@ function SubTags(): JSX.Element { onChange={(e) => setTagSearchName(e.target.value.trim())} data-testid="searchByName" autoComplete="off" - required />
@@ -302,7 +301,7 @@ function SubTags(): JSX.Element { > {tCommon('sort')} diff --git a/src/screens/SubTags/SubTagsMocks.ts b/src/screens/SubTags/SubTagsMocks.ts index 250c13253de..5165ea3a53d 100644 --- a/src/screens/SubTags/SubTagsMocks.ts +++ b/src/screens/SubTags/SubTagsMocks.ts @@ -9,6 +9,8 @@ export const MOCKS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -228,6 +230,8 @@ export const MOCKS = [ id: '1', after: '10', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -275,6 +279,8 @@ export const MOCKS = [ variables: { id: 'subTag1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -325,6 +331,142 @@ export const MOCKS = [ }, }, }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchSubTag' } }, + sortedBy: { id: 'DESCENDING' }, + }, + }, + result: { + data: { + getChildTags: { + name: 'userTag 1', + childTags: { + edges: [ + { + node: { + _id: 'searchSubTag1', + name: 'searchSubTag 1', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchSubTag1', + }, + { + node: { + _id: 'searchSubTag2', + name: 'searchSubTag 2', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchSubTag2', + }, + ], + pageInfo: { + startCursor: 'searchSubTag1', + endCursor: 'searchSubTag2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + ancestorTags: [], + }, + }, + }, + }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchSubTag' } }, + sortedBy: { id: 'ASCENDING' }, + }, + }, + result: { + data: { + getChildTags: { + name: 'userTag 1', + childTags: { + edges: [ + { + node: { + _id: 'searchSubTag2', + name: 'searchSubTag 2', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchSubTag2', + }, + { + node: { + _id: 'searchSubTag1', + name: 'searchSubTag 1', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchSubTag1', + }, + ], + pageInfo: { + startCursor: 'searchSubTag2', + endCursor: 'searchSubTag1', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + ancestorTags: [], + }, + }, + }, + }, { request: { query: CREATE_USER_TAG, @@ -351,6 +493,8 @@ export const MOCKS_ERROR_SUB_TAGS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, error: new Error('Mock Graphql Error'),