From eaa3ce16ea37e36e07efbb162b81e21f3182eaa8 Mon Sep 17 00:00:00 2001 From: ayeshoali Date: Wed, 25 Jan 2023 16:53:08 +0500 Subject: [PATCH] test: added test cases for hover card component --- .../comments/CommentsView.test.jsx | 465 +++++++++--------- src/discussions/comments/comment/Comment.jsx | 33 +- src/discussions/common/HoverCard.jsx | 2 +- src/discussions/common/HoverCard.test.jsx | 329 +++++++------ src/discussions/posts/post/LikeButton.jsx | 1 - src/discussions/posts/post/Post.jsx | 32 +- 6 files changed, 453 insertions(+), 409 deletions(-) diff --git a/src/discussions/comments/CommentsView.test.jsx b/src/discussions/comments/CommentsView.test.jsx index 228e63df..69f9a84c 100644 --- a/src/discussions/comments/CommentsView.test.jsx +++ b/src/discussions/comments/CommentsView.test.jsx @@ -230,18 +230,14 @@ describe('CommentsView', () => { }); it('should not allow posting a comment on a closed post', async () => { - const componet = renderComponent(closedPostId); + renderComponent(closedPostId); + await act(async () => { - fireEvent.mouseOver(await waitFor(() => screen.findByText('comment number 5', { exact: false }))); + fireEvent.mouseOver(await waitFor(() => screen.findByText('comment number 3', { exact: false }))); }); - // await waitFor(() => screen.findByText('thread-2', { exact: false })); - screen.debug(componet, 99999999); - expect(screen.queryByRole('button', { name: /add comment/i }, { hidden: false })).toBeDisabled(); - // await act(async () => { - // expect( - // screen.queryByRole('button', { name: /add comment/i }, { hidden: false }) - // ).toBeDisabled(); - // }); + + const addCommentButton = screen.getAllByRole('button', { name: /add comment/i }, { hidden: false })[0]; + expect(addCommentButton).toBeDisabled(); }); it('should allow editing an existing comment', async () => { @@ -322,7 +318,7 @@ describe('CommentsView', () => { setupCourseConfig(); renderComponent(discussionPostId); await act(async () => { - fireEvent.mouseOver(await waitFor(() => screen.findByText('Thread-1', { exact: false }))); + fireEvent.mouseOver(await waitFor(() => screen.findByTestId('post-thread-1'))); }); await act(async () => { fireEvent.click( @@ -353,7 +349,7 @@ describe('CommentsView', () => { setupCourseConfig(false); renderComponent(discussionPostId); await act(async () => { - fireEvent.mouseOver(await waitFor(() => screen.findByText('Thread-1', { exact: false }))); + fireEvent.mouseOver(await waitFor(() => screen.findByTestId('post-thread-1'))); }); await act(async () => { fireEvent.click( @@ -396,7 +392,7 @@ describe('CommentsView', () => { setupCourseConfig(false); renderComponent(discussionPostId); await act(async () => { - fireEvent.mouseOver(await waitFor(() => screen.findByText('Thread-1', { exact: false }))); + fireEvent.mouseOver(await waitFor(() => screen.findByTestId('post-thread-1'))); }); await act(async () => { fireEvent.click( @@ -413,7 +409,7 @@ describe('CommentsView', () => { it('should allow pinning the post', async () => { renderComponent(discussionPostId); await act(async () => { - fireEvent.mouseOver(await waitFor(() => screen.findByText('Thread-1', { exact: false }))); + fireEvent.mouseOver(await waitFor(() => screen.findByTestId('post-thread-1'))); }); await act(async () => { fireEvent.click( @@ -430,7 +426,7 @@ describe('CommentsView', () => { it('should allow reporting the post', async () => { renderComponent(discussionPostId); await act(async () => { - fireEvent.mouseOver(await waitFor(() => screen.findByText('Thread-1', { exact: false }))); + fireEvent.mouseOver(await waitFor(() => screen.findByTestId('post-thread-1'))); }); await act(async () => { fireEvent.click( @@ -504,264 +500,257 @@ describe('CommentsView', () => { }); }); - // describe('for discussion thread', () => { - // const findLoadMoreCommentsButton = () => screen.findByTestId('load-more-comments'); + describe('for discussion thread', () => { + const findLoadMoreCommentsButton = () => screen.findByTestId('load-more-comments'); - // it('shown post not found when post id does not belong to course', async () => { - // renderComponent('unloaded-id'); - // expect(await screen.findByText('Thread not found', { exact: true })) - // .toBeInTheDocument(); - // }); + it('shown post not found when post id does not belong to course', async () => { + renderComponent('unloaded-id'); + expect(await screen.findByText('Thread not found', { exact: true })) + .toBeInTheDocument(); + }); - // it('initially loads only the first page', async () => { - // renderComponent(discussionPostId); - // expect(await screen.findByText('comment number 1', { exact: false })) - // .toBeInTheDocument(); - // expect(screen.queryByText('comment number 2', { exact: false })) - // .not - // .toBeInTheDocument(); - // }); + it('initially loads only the first page', async () => { + renderComponent(discussionPostId); + expect(await screen.findByText('comment number 1', { exact: false })) + .toBeInTheDocument(); + expect(screen.queryByText('comment number 2', { exact: false })) + .not + .toBeInTheDocument(); + }); - // it('pressing load more button will load next page of comments', async () => { - // renderComponent(discussionPostId); + it('pressing load more button will load next page of comments', async () => { + renderComponent(discussionPostId); - // const loadMoreButton = await findLoadMoreCommentsButton(); - // fireEvent.click(loadMoreButton); + const loadMoreButton = await findLoadMoreCommentsButton(); + fireEvent.click(loadMoreButton); - // await screen.findByText('comment number 1', { exact: false }); - // await screen.findByText('comment number 2', { exact: false }); - // }); + await screen.findByText('comment number 1', { exact: false }); + await screen.findByText('comment number 2', { exact: false }); + }); - // it('newly loaded comments are appended to the old ones', async () => { - // renderComponent(discussionPostId); + it('newly loaded comments are appended to the old ones', async () => { + renderComponent(discussionPostId); - // const loadMoreButton = await findLoadMoreCommentsButton(); - // fireEvent.click(loadMoreButton); + const loadMoreButton = await findLoadMoreCommentsButton(); + fireEvent.click(loadMoreButton); - // await screen.findByText('comment number 1', { exact: false }); - // // check that comments from the first page are also displayed - // expect(screen.queryByText('comment number 2', { exact: false })) - // .toBeInTheDocument(); - // }); + await screen.findByText('comment number 1', { exact: false }); + // check that comments from the first page are also displayed + expect(screen.queryByText('comment number 2', { exact: false })) + .toBeInTheDocument(); + }); - // it('load more button is hidden when no more comments pages to load', async () => { - // const totalPages = 2; - // renderComponent(discussionPostId); + it('load more button is hidden when no more comments pages to load', async () => { + const totalPages = 2; + renderComponent(discussionPostId); - // const loadMoreButton = await findLoadMoreCommentsButton(); - // for (let page = 1; page < totalPages; page++) { - // fireEvent.click(loadMoreButton); - // } + const loadMoreButton = await findLoadMoreCommentsButton(); + for (let page = 1; page < totalPages; page++) { + fireEvent.click(loadMoreButton); + } - // await screen.findByText('comment number 2', { exact: false }); - // await expect(findLoadMoreCommentsButton()) - // .rejects - // .toThrow(); - // }); - // }); + await screen.findByText('comment number 2', { exact: false }); + await expect(findLoadMoreCommentsButton()) + .rejects + .toThrow(); + }); + }); - // describe('for question thread', () => { - // const findLoadMoreCommentsButtons = () => screen.findAllByTestId('load-more-comments'); + describe('for question thread', () => { + const findLoadMoreCommentsButtons = () => screen.findAllByTestId('load-more-comments'); - // it('initially loads only the first page', async () => { - // act(() => renderComponent(questionPostId)); - // expect(await screen.findByText('comment number 3', { exact: false })) - // .toBeInTheDocument(); - // expect(await screen.findByText('endorsed comment number 5', { exact: false })) - // .toBeInTheDocument(); - // expect(screen.queryByText('comment number 4', { exact: false })) - // .not - // .toBeInTheDocument(); - // }); + it('initially loads only the first page', async () => { + act(() => renderComponent(questionPostId)); + expect(await screen.findByText('comment number 3', { exact: false })) + .toBeInTheDocument(); + expect(await screen.findByText('endorsed comment number 5', { exact: false })) + .toBeInTheDocument(); + expect(screen.queryByText('comment number 4', { exact: false })) + .not + .toBeInTheDocument(); + }); - // it('pressing load more button will load next page of comments', async () => { - // act(() => { - // renderComponent(questionPostId); - // }); + it('pressing load more button will load next page of comments', async () => { + act(() => { + renderComponent(questionPostId); + }); - // const [loadMoreButtonEndorsed, loadMoreButtonUnendorsed] = await findLoadMoreCommentsButtons(); - // // Both load more buttons should show - // expect(await findLoadMoreCommentsButtons()).toHaveLength(2); - // expect(await screen.findByText('unendorsed comment number 3', { exact: false })) - // .toBeInTheDocument(); - // expect(await screen.findByText('endorsed comment number 5', { exact: false })) - // .toBeInTheDocument(); - // // Comments from next page should not be loaded yet. - // expect(await screen.queryByText('endorsed comment number 6', { exact: false })) - // .not - // .toBeInTheDocument(); - // expect(await screen.queryByText('unendorsed comment number 4', { exact: false })) - // .not - // .toBeInTheDocument(); + const [loadMoreButtonEndorsed, loadMoreButtonUnendorsed] = await findLoadMoreCommentsButtons(); + // Both load more buttons should show + expect(await findLoadMoreCommentsButtons()).toHaveLength(2); + expect(await screen.findByText('unendorsed comment number 3', { exact: false })) + .toBeInTheDocument(); + expect(await screen.findByText('endorsed comment number 5', { exact: false })) + .toBeInTheDocument(); + // Comments from next page should not be loaded yet. + expect(await screen.queryByText('endorsed comment number 6', { exact: false })) + .not + .toBeInTheDocument(); + expect(await screen.queryByText('unendorsed comment number 4', { exact: false })) + .not + .toBeInTheDocument(); - // await act(async () => { - // fireEvent.click(loadMoreButtonEndorsed); - // }); - // // Endorsed comment from next page should be loaded now. - // await waitFor(() => expect(screen.queryByText('endorsed comment number 6', { exact: false })) - // .toBeInTheDocument()); - // // Unendorsed comment from next page should not be loaded yet. - // expect(await screen.queryByText('unendorsed comment number 4', { exact: false })) - // .not - // .toBeInTheDocument(); - // // Now only one load more buttons should show, for unendorsed comments - // expect(await findLoadMoreCommentsButtons()).toHaveLength(1); - // await act(async () => { - // fireEvent.click(loadMoreButtonUnendorsed); - // }); - // // Unendorsed comment from next page should be loaded now. - // await waitFor(() => expect(screen.queryByText('unendorsed comment number 4', { exact: false })) - // .toBeInTheDocument()); - // await expect(findLoadMoreCommentsButtons()).rejects.toThrow(); - // }); - // }); + await act(async () => { + fireEvent.click(loadMoreButtonEndorsed); + }); + // Endorsed comment from next page should be loaded now. + await waitFor(() => expect(screen.queryByText('endorsed comment number 6', { exact: false })) + .toBeInTheDocument()); + // Unendorsed comment from next page should not be loaded yet. + expect(await screen.queryByText('unendorsed comment number 4', { exact: false })) + .not + .toBeInTheDocument(); + // Now only one load more buttons should show, for unendorsed comments + expect(await findLoadMoreCommentsButtons()).toHaveLength(1); + await act(async () => { + fireEvent.click(loadMoreButtonUnendorsed); + }); + // Unendorsed comment from next page should be loaded now. + await waitFor(() => expect(screen.queryByText('unendorsed comment number 4', { exact: false })) + .toBeInTheDocument()); + await expect(findLoadMoreCommentsButtons()).rejects.toThrow(); + }); + }); - // describe('comments responses', () => { - // const findLoadMoreCommentsResponsesButton = () => screen.findByTestId('load-more-comments-responses'); + describe('comments responses', () => { + const findLoadMoreCommentsResponsesButton = () => screen.findByTestId('load-more-comments-responses'); - // it('initially loads only the first page', async () => { - // renderComponent(discussionPostId); + it('initially loads only the first page', async () => { + renderComponent(discussionPostId); - // await waitFor(() => screen.findByText('comment number 7', { exact: false })); - // expect(screen.queryByText('comment number 8', { exact: false })).not.toBeInTheDocument(); - // }); + await waitFor(() => screen.findByText('comment number 7', { exact: false })); + expect(screen.queryByText('comment number 8', { exact: false })).not.toBeInTheDocument(); + }); - // it('pressing load more button will load next page of responses', async () => { - // renderComponent(discussionPostId); + it('pressing load more button will load next page of responses', async () => { + renderComponent(discussionPostId); - // const loadMoreButton = await findLoadMoreCommentsResponsesButton(); - // await act(async () => { - // fireEvent.click(loadMoreButton); - // }); + const loadMoreButton = await findLoadMoreCommentsResponsesButton(); + await act(async () => { + fireEvent.click(loadMoreButton); + }); - // await screen.findByText('comment number 8', { exact: false }); - // }); + await screen.findByText('comment number 8', { exact: false }); + }); - // it('newly loaded responses are appended to the old ones', async () => { - // renderComponent(discussionPostId); + it('newly loaded responses are appended to the old ones', async () => { + renderComponent(discussionPostId); - // const loadMoreButton = await findLoadMoreCommentsResponsesButton(); - // await act(async () => { - // fireEvent.click(loadMoreButton); - // }); + const loadMoreButton = await findLoadMoreCommentsResponsesButton(); + await act(async () => { + fireEvent.click(loadMoreButton); + }); - // await screen.findByText('comment number 8', { exact: false }); - // // check that comments from the first page are also displayed - // expect(screen.queryByText('comment number 7', { exact: false })).toBeInTheDocument(); - // }); + await screen.findByText('comment number 8', { exact: false }); + // check that comments from the first page are also displayed + expect(screen.queryByText('comment number 7', { exact: false })).toBeInTheDocument(); + }); - // it('load more button is hidden when no more responses pages to load', async () => { - // const totalPages = 2; - // renderComponent(discussionPostId); + it('load more button is hidden when no more responses pages to load', async () => { + const totalPages = 2; + renderComponent(discussionPostId); - // const loadMoreButton = await findLoadMoreCommentsResponsesButton(); - // for (let page = 1; page < totalPages; page++) { - // act(() => { - // fireEvent.click(loadMoreButton); - // }); - // } + const loadMoreButton = await findLoadMoreCommentsResponsesButton(); + for (let page = 1; page < totalPages; page++) { + act(() => { + fireEvent.click(loadMoreButton); + }); + } - // await screen.findByText('comment number 8', { exact: false }); - // await expect(findLoadMoreCommentsResponsesButton()) - // .rejects - // .toThrow(); - // }); + await screen.findByText('comment number 8', { exact: false }); + await expect(findLoadMoreCommentsResponsesButton()) + .rejects + .toThrow(); + }); - // it('handles liking a comment', async () => { - // renderComponent(discussionPostId); + it('handles liking a comment', async () => { + renderComponent(discussionPostId); - // // Wait for the content to load - // await act(async () => { - // fireEvent.mouseOver(await waitFor(() => screen.findByText('comment number 7', { exact: false }))); - // }); - // const view = screen.getByTestId('comment-comment-1'); + // Wait for the content to load + await act(async () => { + fireEvent.mouseOver(await waitFor(() => screen.findByText('comment number 7', { exact: false }))); + }); + const view = screen.getByTestId('comment-comment-1'); - // const likeButton = within(view).getByRole('button', { name: /like/i }); - // await act(async () => { - // fireEvent.click(likeButton); - // }); - // expect(axiosMock.history.patch).toHaveLength(2); - // expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ voted: true }); - // }); + const likeButton = within(view).getByRole('button', { name: /like/i }); + await act(async () => { + fireEvent.click(likeButton); + }); + expect(axiosMock.history.patch).toHaveLength(2); + expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ voted: true }); + }); - // it('handles endorsing comments', async () => { - // renderComponent(discussionPostId); - // // Wait for the content to load - // await act(async () => { - // fireEvent.mouseOver(await waitFor(() => screen.findByText('comment number 7', { exact: false }))); - // }); + it('handles endorsing comments', async () => { + renderComponent(discussionPostId); + // Wait for the content to load + await act(async () => { + fireEvent.mouseOver(await waitFor(() => screen.findByText('comment number 7', { exact: false }))); + }); - // // There should be three buttons, one for the post, the second for the - // // comment and the third for a response to that comment - // const actionButtons = screen.queryAllByRole('button', { name: /actions menu/i }); - // await act(async () => { - // fireEvent.click(actionButtons[1]); - // }); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /Endorse/i })); + }); + expect(axiosMock.history.patch).toHaveLength(2); + expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ endorsed: true }); + }); - // await act(async () => { - // fireEvent.click(screen.getByRole('button', { name: /Endorse/i })); - // }); - // expect(axiosMock.history.patch).toHaveLength(2); - // expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ endorsed: true }); - // }); + it('handles reporting comments', async () => { + renderComponent(discussionPostId); + // Wait for the content to load + await act(async () => { + fireEvent.mouseOver(await waitFor(() => screen.findByText('comment number 7', { exact: false }))); + }); - // it('handles reporting comments', async () => { - // renderComponent(discussionPostId); - // // Wait for the content to load - // await act(async () => { - // fireEvent.mouseOver(await waitFor(() => screen.findByText('comment number 7', { exact: false }))); - // }); + // There should be three buttons, one for the post, the second for the + // comment and the third for a response to that comment + const actionButtons = screen.queryAllByRole('button', { name: /actions menu/i }); + await act(async () => { + fireEvent.click(actionButtons[1]); + }); - // // There should be three buttons, one for the post, the second for the - // // comment and the third for a response to that comment - // const actionButtons = screen.queryAllByRole('button', { name: /actions menu/i }); - // await act(async () => { - // fireEvent.click(actionButtons[1]); - // }); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /Report/i })); + }); + expect(screen.queryByRole('dialog', { name: /Report \w+/i, exact: false })).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.queryByRole('button', { name: /Confirm/i })); + }); + expect(screen.queryByRole('dialog', { name: /Report \w+/i, exact: false })).not.toBeInTheDocument(); + expect(axiosMock.history.patch).toHaveLength(2); + expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ abuse_flagged: true }); + }); + }); - // await act(async () => { - // fireEvent.click(screen.getByRole('button', { name: /Report/i })); - // }); - // expect(screen.queryByRole('dialog', { name: /Report \w+/i, exact: false })).toBeInTheDocument(); - // await act(async () => { - // fireEvent.click(screen.queryByRole('button', { name: /Confirm/i })); - // }); - // expect(screen.queryByRole('dialog', { name: /Report \w+/i, exact: false })).not.toBeInTheDocument(); - // expect(axiosMock.history.patch).toHaveLength(2); - // expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ abuse_flagged: true }); - // }); - // }); - - // describe.each([ - // { component: 'post', testId: 'post-thread-1' }, - // { component: 'comment', testId: 'comment-comment-1' }, - // { component: 'reply', testId: 'reply-comment-7' }, - // ])('delete confirmation modal', ({ - // component, - // testId, - // }) => { - // test(`for ${component}`, async () => { - // renderComponent(discussionPostId); - // // Wait for the content to load - // await waitFor(() => expect(screen.queryByText('comment number 7', { exact: false })).toBeInTheDocument()); - // const content = screen.getByTestId(testId); - // await act(async () => { - // fireEvent.mouseOver(screen.getByTestId('post-thread-1')); - // }); - // const actionsButton = within(content).getAllByRole('button', { name: /actions menu/i })[0]; - // await act(async () => { - // fireEvent.click(actionsButton); - // }); - // expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).not.toBeInTheDocument(); - // const deleteButton = within(content).queryByRole('button', { name: /delete/i }); - // await act(async () => { - // fireEvent.click(deleteButton); - // }); - // expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).toBeInTheDocument(); - // await act(async () => { - // fireEvent.click(screen.queryByRole('button', { name: /delete/i })); - // }); - // expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).not.toBeInTheDocument(); - // }); - // }); + describe.each([ + { component: 'post', testId: 'post-thread-1' }, + { component: 'comment', testId: 'comment-comment-1' }, + { component: 'reply', testId: 'reply-comment-7' }, + ])('delete confirmation modal', ({ + component, + testId, + }) => { + test(`for ${component}`, async () => { + renderComponent(discussionPostId); + // Wait for the content to load + await waitFor(() => expect(screen.queryByText('comment number 7', { exact: false })).toBeInTheDocument()); + const content = screen.getByTestId(testId); + await act(async () => { + fireEvent.mouseOver(content); + }); + const actionsButton = within(content).getAllByRole('button', { name: /actions menu/i })[0]; + await act(async () => { + fireEvent.click(actionsButton); + }); + expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).not.toBeInTheDocument(); + const deleteButton = within(content).queryByRole('button', { name: /delete/i }); + await act(async () => { + fireEvent.click(deleteButton); + }); + expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).toBeInTheDocument(); + await act(async () => { + fireEvent.click(screen.queryByRole('button', { name: /delete/i })); + }); + expect(screen.queryByRole('dialog', { name: /delete \w+/i, exact: false })).not.toBeInTheDocument(); + }); + }); }); diff --git a/src/discussions/comments/comment/Comment.jsx b/src/discussions/comments/comment/Comment.jsx index 4c2606ae..dbb96291 100644 --- a/src/discussions/comments/comment/Comment.jsx +++ b/src/discussions/comments/comment/Comment.jsx @@ -43,6 +43,7 @@ function Comment({ const hasMorePages = useSelector(selectCommentHasMorePages(comment.id)); const currentPage = useSelector(selectCommentCurrentPage(comment.id)); const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate(); + const [showHoverCard, setShowHoverCard] = useState(false); const { courseId, } = useContext(DiscussionContext); @@ -91,7 +92,13 @@ function Comment({ ); return (
-
+ {/* eslint-disable jsx-a11y/no-noninteractive-tabindex */} +
setShowHoverCard(true)} + onMouseLeave={() => setShowHoverCard(false)} + onFocus={() => setShowHoverCard(true)} + onBlur={() => setShowHoverCard(false)} > - setReplying(true)} - onLike={() => dispatch(editComment(comment.id, { voted: !comment.voted }))} - addResponseCommentButtonMessage={intl.formatMessage(messages.addComment)} - isClosedPost={isClosedPost} - endorseIcons={endorseIcons} - /> + {showHoverCard && ( + setReplying(true)} + onLike={() => dispatch(editComment(comment.id, { voted: !comment.voted }))} + addResponseCommentButtonMessage={intl.formatMessage(messages.addComment)} + isClosedPost={isClosedPost} + endorseIcons={endorseIcons} + /> + )} {isEditing diff --git a/src/discussions/common/HoverCard.jsx b/src/discussions/common/HoverCard.jsx index 925c689d..b06f4911 100644 --- a/src/discussions/common/HoverCard.jsx +++ b/src/discussions/common/HoverCard.jsx @@ -33,7 +33,7 @@ function HoverCard({ return (
{userCanAddThreadInBlackoutDate && ( diff --git a/src/discussions/common/HoverCard.test.jsx b/src/discussions/common/HoverCard.test.jsx index 532e9385..6f11b3b2 100644 --- a/src/discussions/common/HoverCard.test.jsx +++ b/src/discussions/common/HoverCard.test.jsx @@ -1,157 +1,194 @@ -// import { -// act, fireEvent, render, screen, waitFor, within, -// } from '@testing-library/react'; -// import MockAdapter from 'axios-mock-adapter'; -// import { IntlProvider } from 'react-intl'; -// import { MemoryRouter, Route } from 'react-router'; -// import { Factory } from 'rosie'; +import { + render, screen, waitFor, within, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import MockAdapter from 'axios-mock-adapter'; +import { IntlProvider } from 'react-intl'; +import { MemoryRouter, Route } from 'react-router'; +import { Factory } from 'rosie'; -// import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform'; -// import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -// import { AppProvider } from '@edx/frontend-platform/react'; +import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { AppProvider } from '@edx/frontend-platform/react'; -// import { initializeStore } from '../../store'; -// import { executeThunk } from '../../test-utils'; -// import { getCourseConfigApiUrl } from '../data/api'; -// import { fetchCourseConfig } from '../data/thunks'; -// import DiscussionContent from '../discussions-home/DiscussionContent'; -// import { getThreadsApiUrl } from '../posts/data/api'; -// import { fetchThreads } from '../posts/data/thunks'; -// import { getCommentsApiUrl } from './data/api'; -// import { DiscussionContext } from './context'; +import { initializeStore } from '../../store'; +import { executeThunk } from '../../test-utils'; +import { getCommentsApiUrl } from '../comments/data/api'; +import DiscussionContent from '../discussions-home/DiscussionContent'; +import { getThreadsApiUrl } from '../posts/data/api'; +import { fetchThreads } from '../posts/data/thunks'; +import { DiscussionContext } from './context'; -// import '../posts/data/__factories__'; -// import './data/__factories__'; +import '../posts/data/__factories__'; +import '../comments/data/__factories__'; -// const courseConfigApiUrl = getCourseConfigApiUrl(); -// const commentsApiUrl = getCommentsApiUrl(); -// const threadsApiUrl = getThreadsApiUrl(); -// const discussionPostId = 'thread-1'; -// const questionPostId = 'thread-2'; -// const closedPostId = 'thread-2'; -// const courseId = 'course-v1:edX+TestX+Test_Course'; -// let store; -// let axiosMock; -// let testLocation; +const commentsApiUrl = getCommentsApiUrl(); +const threadsApiUrl = getThreadsApiUrl(); +const discussionPostId = 'thread-1'; +const questionPostId = 'thread-2'; +const courseId = 'course-v1:edX+TestX+Test_Course'; +let store; +let axiosMock; +let container; -// function mockAxiosReturnPagedComments() { -// [null, false, true].forEach(endorsed => { -// const postId = endorsed === null ? discussionPostId : questionPostId; -// [1, 2].forEach(page => { -// axiosMock -// .onGet(commentsApiUrl, { -// params: { -// thread_id: postId, -// page, -// page_size: undefined, -// requested_fields: 'profile_image', -// endorsed, -// }, -// }) -// .reply(200, Factory.build('commentsResult', { can_delete: true }, { -// threadId: postId, -// page, -// pageSize: 1, -// count: 2, -// endorsed, -// childCount: page === 1 ? 2 : 0, -// })); -// }); -// }); -// } +function mockAxiosReturnPagedComments() { + [null, false, true].forEach(endorsed => { + const postId = endorsed === null ? discussionPostId : questionPostId; + [1, 2].forEach(page => { + axiosMock + .onGet(commentsApiUrl, { + params: { + thread_id: postId, + page, + page_size: undefined, + requested_fields: 'profile_image', + endorsed, + }, + }) + .reply(200, Factory.build('commentsResult', { can_delete: true }, { + threadId: postId, + page, + pageSize: 1, + count: 2, + endorsed, + childCount: page === 1 ? 2 : 0, + })); + }); + }); +} -// function mockAxiosReturnPagedCommentsResponses() { -// const parentId = 'comment-1'; -// const commentsResponsesApiUrl = `${commentsApiUrl}${parentId}/`; -// const paramsTemplate = { -// page: undefined, -// page_size: undefined, -// requested_fields: 'profile_image', -// }; +function mockAxiosReturnPagedCommentsResponses() { + const parentId = 'comment-1'; + const commentsResponsesApiUrl = `${commentsApiUrl}${parentId}/`; + const paramsTemplate = { + page: undefined, + page_size: undefined, + requested_fields: 'profile_image', + }; -// for (let page = 1; page <= 2; page++) { -// axiosMock -// .onGet(commentsResponsesApiUrl, { params: { ...paramsTemplate, page } }) -// .reply(200, Factory.build('commentsResult', null, { -// parentId, -// page, -// pageSize: 1, -// count: 2, -// })); -// } -// } + for (let page = 1; page <= 2; page++) { + axiosMock + .onGet(commentsResponsesApiUrl, { params: { ...paramsTemplate, page } }) + .reply(200, Factory.build('commentsResult', null, { + parentId, + page, + pageSize: 1, + count: 2, + })); + } +} -// function renderComponent(postId) { -// render( -// -// -// -// -// -// { -// testLocation = location; -// return null; -// }} -// /> -// -// -// -// , -// ); -// } +function renderComponent(postId) { + const wrapper = render( + + + + + + + + + + , + ); + container = wrapper.container; + return container; +} -// describe('HoverCard', () => { -// beforeEach(() => { -// initializeMockApp({ -// authenticatedUser: { -// userId: 3, -// username: 'abc123', -// administrator: true, -// roles: [], -// }, -// }); +describe('HoverCard', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); -// store = initializeStore(); -// Factory.resetAll(); -// axiosMock = new MockAdapter(getAuthenticatedHttpClient()); -// axiosMock.onGet(threadsApiUrl) -// .reply(200, Factory.build('threadsResult')); -// axiosMock.onPatch(new RegExp(`${commentsApiUrl}*`)).reply(({ -// url, -// data, -// }) => { -// const commentId = url.match(/comments\/(?[a-z1-9-]+)\//).groups.id; -// const { -// rawBody, -// } = camelCaseObject(JSON.parse(data)); -// return [200, Factory.build('comment', { -// id: commentId, -// rendered_body: rawBody, -// raw_body: rawBody, -// })]; -// }); -// axiosMock.onPost(commentsApiUrl) -// .reply(({ data }) => { -// const { -// rawBody, -// threadId, -// } = camelCaseObject(JSON.parse(data)); -// return [200, Factory.build( -// 'comment', -// { -// rendered_body: rawBody, -// raw_body: rawBody, -// thread_id: threadId, -// }, -// )]; -// }); + store = initializeStore(); + Factory.resetAll(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock.onGet(threadsApiUrl) + .reply(200, Factory.build('threadsResult')); + axiosMock.onPatch(new RegExp(`${commentsApiUrl}*`)).reply(({ + url, + data, + }) => { + const commentId = url.match(/comments\/(?[a-z1-9-]+)\//).groups.id; + const { + rawBody, + } = camelCaseObject(JSON.parse(data)); + return [200, Factory.build('comment', { + id: commentId, + rendered_body: rawBody, + raw_body: rawBody, + })]; + }); + axiosMock.onPost(commentsApiUrl) + .reply(({ data }) => { + const { + rawBody, + threadId, + } = camelCaseObject(JSON.parse(data)); + return [200, Factory.build( + 'comment', + { + rendered_body: rawBody, + raw_body: rawBody, + thread_id: threadId, + }, + )]; + }); -// executeThunk(fetchThreads(courseId), store.dispatch, store.getState); -// mockAxiosReturnPagedComments(); -// mockAxiosReturnPagedCommentsResponses(); -// }); -// }); + executeThunk(fetchThreads(courseId), store.dispatch, store.getState); + mockAxiosReturnPagedComments(); + mockAxiosReturnPagedCommentsResponses(); + }); + + test('it should show hover card when hovered on post', async () => { + renderComponent(discussionPostId); + const post = screen.getByTestId('post-thread-1'); + userEvent.hover(post); + expect(screen.getByTestId('hover-card')).toBeInTheDocument(); + }); + + test('it should show hover card when hovered on comment', async () => { + renderComponent(discussionPostId); + const comment = await waitFor(() => screen.findByText('comment number 1', { exact: false })); + userEvent.hover(comment); + expect(screen.getByTestId('hover-card')).toBeInTheDocument(); + }); + + test('it should not show hover card when post and comment not hovered', async () => { + renderComponent(discussionPostId); + expect(screen.queryByTestId('hover-card')).not.toBeInTheDocument(); + }); + + test('it should show add response, like, follow and actions menu for hovered post', async () => { + renderComponent(discussionPostId); + const post = screen.getByTestId('post-thread-1'); + userEvent.hover(post); + const view = screen.getByTestId('hover-card'); + expect(within(view).queryByRole('button', { name: /Add response/i })).toBeInTheDocument(); + expect(within(view).getByRole('button', { name: /like/i })).toBeInTheDocument(); + expect(within(view).queryByRole('button', { name: /follow/i })).toBeInTheDocument(); + expect(within(view).queryByRole('button', { name: /actions menu/i })).toBeInTheDocument(); + }); + + test('it should show add comment, Endorse, like and actions menu Buttons for hovered comment', async () => { + renderComponent(questionPostId); + const comment = await waitFor(() => screen.findByText('unendorsed comment number 3', { exact: false })); + userEvent.hover(comment); + const view = screen.getByTestId('hover-card'); + expect(screen.getByTestId('hover-card')).toBeInTheDocument(); + expect(within(view).queryByRole('button', { name: /Add comment/i })).toBeInTheDocument(); + expect(within(view).getByRole('button', { name: /Endorse/i })).toBeInTheDocument(); + expect(within(view).queryByRole('button', { name: /like/i })).toBeInTheDocument(); + expect(within(view).queryByRole('button', { name: /actions menu/i })).toBeInTheDocument(); + }); +}); diff --git a/src/discussions/posts/post/LikeButton.jsx b/src/discussions/posts/post/LikeButton.jsx index 70b9ef37..fa9b9392 100644 --- a/src/discussions/posts/post/LikeButton.jsx +++ b/src/discussions/posts/post/LikeButton.jsx @@ -39,7 +39,6 @@ function LikeButton({ alt="Like" iconAs={Icon} iconClassNames="like-icon-dimentions" - />
diff --git a/src/discussions/posts/post/Post.jsx b/src/discussions/posts/post/Post.jsx index d2470838..76609eb2 100644 --- a/src/discussions/posts/post/Post.jsx +++ b/src/discussions/posts/post/Post.jsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useState } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; @@ -41,7 +41,7 @@ function Post({ const [isDeleting, showDeleteConfirmation, hideDeleteConfirmation] = useToggle(false); const [isReporting, showReportConfirmation, hideReportConfirmation] = useToggle(false); const [isClosing, showClosePostModal, hideClosePostModal] = useToggle(false); - + const [showHoverCard, setShowHoverCard] = useState(false); const handleAbusedFlag = () => { if (post.abuseFlagged) { dispatch(updateExistingThread(post.id, { flagged: !post.abuseFlagged })); @@ -92,6 +92,11 @@ function Post({
setShowHoverCard(true)} + onMouseLeave={() => setShowHoverCard(false)} + onFocus={() => setShowHoverCard(true)} + onBlur={() => setShowHoverCard(false)} > )} - dispatch(updateExistingThread(post.id, { voted: !post.voted }))} - onFollow={() => dispatch(updateExistingThread(post.id, { following: !post.following }))} - isClosedPost={post.closed} - /> - + {showHoverCard && ( + dispatch(updateExistingThread(post.id, { voted: !post.voted }))} + onFollow={() => dispatch(updateExistingThread(post.id, { following: !post.following }))} + isClosedPost={post.closed} + /> + )}
@@ -171,7 +177,7 @@ Post.propTypes = { intl: intlShape.isRequired, post: postShape.isRequired, preview: PropTypes.bool, - handleAddResponseButton: PropTypes.objectOf(PropTypes.func).isRequired, + handleAddResponseButton: PropTypes.func.isRequired, }; Post.defaultProps = {