diff --git a/src/components/UserPortal/CommentCard/CommentCard.spec.tsx b/src/components/UserPortal/CommentCard/CommentCard.spec.tsx index 112fd1bcf5..57a708f793 100644 --- a/src/components/UserPortal/CommentCard/CommentCard.spec.tsx +++ b/src/components/UserPortal/CommentCard/CommentCard.spec.tsx @@ -12,6 +12,7 @@ import userEvent from '@testing-library/user-event'; import { LIKE_COMMENT, UNLIKE_COMMENT } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; import { vi } from 'vitest'; +import { toast } from 'react-toastify'; /** * Unit tests for the CommentCard component. @@ -26,6 +27,12 @@ import { vi } from 'vitest'; */ const { getItem, setItem } = useLocalStorage(); +vi.mock('react-toastify', () => ({ + toast: { + error: vi.fn(), + }, +})); + async function wait(ms = 100): Promise { await act(() => { return new Promise((resolve) => { @@ -67,11 +74,30 @@ const MOCKS = [ }, ]; +const defaultProps = { + id: '1', + creator: { + id: '1', + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + }, + likeCount: 1, + likedBy: [{ id: '1' }], + text: 'testComment', + handleLikeComment: vi.fn(), + handleDislikeComment: vi.fn(), +}; + const handleLikeComment = vi.fn(); const handleDislikeComment = vi.fn(); const link = new StaticMockLink(MOCKS, true); describe('Testing CommentCard Component [User Portal]', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + afterEach(async () => { await act(async () => { await i18nForTest.changeLanguage('en'); @@ -249,4 +275,323 @@ describe('Testing CommentCard Component [User Portal]', () => { setItem('userId', beforeUserId); } }); + + it('should handle like mutation error correctly', async () => { + const errorMock = { + request: { + query: LIKE_COMMENT, + variables: { commentId: '1' }, + }, + error: new Error('Failed to like comment'), + }; + + const link = new StaticMockLink([errorMock], true); + setItem('userId', '2'); // Set user as not having liked the comment + + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('likeCommentBtn')); + await wait(); + + expect(toast.error).toHaveBeenCalled(); + expect(defaultProps.handleLikeComment).not.toHaveBeenCalled(); + }); + + it('should handle unlike mutation error correctly', async () => { + const errorMock = { + request: { + query: UNLIKE_COMMENT, + variables: { commentId: '1' }, + }, + error: new Error('Failed to unlike comment'), + }; + + const link = new StaticMockLink([errorMock], true); + setItem('userId', '1'); // Set user as having liked the comment + + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('likeCommentBtn')); + await wait(); + + expect(toast.error).toHaveBeenCalled(); + expect(defaultProps.handleDislikeComment).not.toHaveBeenCalled(); + }); + + it('should successfully like a comment and update UI', async () => { + const successMock = { + request: { + query: LIKE_COMMENT, + variables: { commentId: '1' }, + }, + result: { + data: { + likeComment: { + _id: '1', + }, + }, + }, + }; + + const link = new StaticMockLink([successMock], true); + setItem('userId', '2'); // Set user as not having liked the comment + + const { container } = render( + + + + + + + + + , + ); + + await wait(); + + const initialLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + userEvent.click(screen.getByTestId('likeCommentBtn')); + await wait(); + + const updatedLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + expect(updatedLikes).not.toBe(initialLikes); + expect(defaultProps.handleLikeComment).toHaveBeenCalledWith('1'); + }); + + it('should successfully unlike a comment and update UI', async () => { + const successMock = { + request: { + query: UNLIKE_COMMENT, + variables: { commentId: '1' }, + }, + result: { + data: { + unlikeComment: { + _id: '1', + }, + }, + }, + }; + + const link = new StaticMockLink([successMock], true); + setItem('userId', '1'); // Set user as having liked the comment + + const { container } = render( + + + + + + + + + , + ); + + await wait(); + + const initialLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + userEvent.click(screen.getByTestId('likeCommentBtn')); + await wait(); + + const updatedLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + expect(updatedLikes).not.toBe(initialLikes); + expect(defaultProps.handleDislikeComment).toHaveBeenCalledWith('1'); + }); + + it('should show loading state while mutation is in progress', async () => { + const slowMock = { + request: { + query: LIKE_COMMENT, + variables: { commentId: '1' }, + }, + result: { + data: { + likeComment: { + _id: '1', + }, + }, + }, + delay: 100, + }; + + const link = new StaticMockLink([slowMock], true); + setItem('userId', '2'); // Set user as not having liked the comment + + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByTestId('likeCommentBtn')); + + // HourglassBottomIcon should be visible during loading + expect( + document.querySelector('[data-testid="HourglassBottomIcon"]'), + ).toBeInTheDocument(); + + await wait(150); + + // After loading, ThumbUpIcon should be visible + expect( + document.querySelector('[data-testid="ThumbUpIcon"]'), + ).toBeInTheDocument(); + }); + + it('should not update state if mutation returns no data', async () => { + const noDataMock = { + request: { + query: LIKE_COMMENT, + variables: { commentId: '1' }, + }, + result: { + data: null, + }, + }; + + const link = new StaticMockLink([noDataMock], true); + setItem('userId', '2'); // Set user as not having liked the comment + + const { container } = render( + + + + + + + + + , + ); + + await wait(); + + const initialLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + userEvent.click(screen.getByTestId('likeCommentBtn')); + await wait(); + + const updatedLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + expect(updatedLikes).toBe(initialLikes); + expect(defaultProps.handleLikeComment).not.toHaveBeenCalled(); + }); + + it('should handle successful mutation with empty data for unlike', async () => { + const emptyDataMock = { + request: { + query: UNLIKE_COMMENT, + variables: { commentId: '1' }, + }, + result: { + data: { + unlikeComment: null, + }, + }, + }; + + const link = new StaticMockLink([emptyDataMock], true); + setItem('userId', '1'); // Set user as having liked the comment + + const { container } = render( + + + + + + + + + , + ); + + const initialLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + + await wait(); + + userEvent.click(screen.getByTestId('likeCommentBtn')); + await wait(); + + // Verify that the likes count hasn't changed + const updatedLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + expect(updatedLikes).toBe(initialLikes); + + // Verify that the callback wasn't called + expect(defaultProps.handleDislikeComment).not.toHaveBeenCalled(); + }); + + it('should handle successful mutation with empty data for like', async () => { + const emptyDataMock = { + request: { + query: LIKE_COMMENT, + variables: { commentId: '1' }, + }, + result: { + data: { + likeComment: null, + }, + }, + }; + + const link = new StaticMockLink([emptyDataMock], true); + setItem('userId', '2'); // Set user as not having liked the comment + + const { container } = render( + + + + + + + + + , + ); + + const initialLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + + await wait(); + + userEvent.click(screen.getByTestId('likeCommentBtn')); + await wait(); + + // Verify that the likes count hasn't changed + const updatedLikes = container.textContent?.match(/\d+ Likes/)?.[0]; + expect(updatedLikes).toBe(initialLikes); + + // Verify that the callback wasn't called + expect(defaultProps.handleLikeComment).not.toHaveBeenCalled(); + }); }); diff --git a/src/components/UserPortal/CommentCard/CommentCard.tsx b/src/components/UserPortal/CommentCard/CommentCard.tsx index 9e8c46d241..ace89d33d0 100644 --- a/src/components/UserPortal/CommentCard/CommentCard.tsx +++ b/src/components/UserPortal/CommentCard/CommentCard.tsx @@ -84,14 +84,12 @@ function commentCard(props: InterfaceCommentCardProps): JSX.Element { commentId: props.id, }, }); - /* istanbul ignore next */ - if (data) { + if (data && data.unlikeComment && data.unlikeComment._id) { setLikes((likes) => likes - 1); setIsLikedByUser(false); props.handleDislikeComment(props.id); } } catch (error: unknown) { - /* istanbul ignore next */ toast.error(error as string); } } else { @@ -101,14 +99,13 @@ function commentCard(props: InterfaceCommentCardProps): JSX.Element { commentId: props.id, }, }); - /* istanbul ignore next */ - if (data) { + + if (data && data.likeComment && data.likeComment._id) { setLikes((likes) => likes + 1); setIsLikedByUser(true); props.handleLikeComment(props.id); } } catch (error: unknown) { - /* istanbul ignore next */ toast.error(error as string); } }