diff --git a/src/discussions/post-comments/PostCommentsView.jsx b/src/discussions/post-comments/PostCommentsView.jsx index e8e17dac..f5bb12c4 100644 --- a/src/discussions/post-comments/PostCommentsView.jsx +++ b/src/discussions/post-comments/PostCommentsView.jsx @@ -9,9 +9,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { useIntl } from '@edx/frontend-platform/i18n'; import Spinner from '../../components/Spinner'; -import { - EndorsementStatus, PostsPages, ThreadType, -} from '../../data/constants'; +import { PostsPages } from '../../data/constants'; import useDispatchWithState from '../../data/hooks'; import DiscussionContext from '../common/context'; import { useIsOnDesktop } from '../data/hooks'; @@ -127,15 +125,7 @@ const PostCommentsView = () => { )}> {!!commentsCount && } - {type === ThreadType.DISCUSSION && ( - - )} - {type === ThreadType.QUESTION && ( - <> - - - - )} + ); diff --git a/src/discussions/post-comments/PostCommentsView.test.jsx b/src/discussions/post-comments/PostCommentsView.test.jsx index 53946c8b..9f858890 100644 --- a/src/discussions/post-comments/PostCommentsView.test.jsx +++ b/src/discussions/post-comments/PostCommentsView.test.jsx @@ -12,7 +12,7 @@ import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { AppProvider } from '@edx/frontend-platform/react'; -import { getApiBaseUrl } from '../../data/constants'; +import { getApiBaseUrl, ThreadType } from '../../data/constants'; import { initializeStore } from '../../store'; import executeThunk from '../../test-utils'; import { getCohortsApiUrl } from '../cohorts/data/api'; @@ -50,10 +50,10 @@ let testLocation; let container; let unmount; -async function mockAxiosReturnPagedComments(threadId, endorsed = false, page = 1, count = 2) { +async function mockAxiosReturnPagedComments(threadId, threadType = ThreadType.DISCUSSION, page = 1, count = 2) { axiosMock.onGet(commentsApiUrl).reply(200, Factory.build('commentsResult', { can_delete: true }, { threadId, - endorsed, + threadType, pageSize: 1, count, childCount: page === 1 ? 2 : 0, @@ -76,6 +76,7 @@ async function mockAxiosReturnPagedCommentsResponses() { Factory.build('commentsResult', null, { threadId: discussionPostId, parentId, + endorsed: false, page, pageSize: 1, count: 2, @@ -201,6 +202,7 @@ describe('ThreadView', () => { id: commentId, rendered_body: rawBody, raw_body: rawBody, + endorsed: false, })]; }); axiosMock.onPost(commentsApiUrl).reply(({ data }) => { @@ -209,6 +211,7 @@ describe('ThreadView', () => { rendered_body: rawBody, raw_body: rawBody, thread_id: threadId, + endorsed: false, })]; }); axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, { isPostingEnabled: true }); @@ -230,9 +233,9 @@ describe('ThreadView', () => { expect(JSON.parse(axiosMock.history.patch[axiosMock.history.patch.length - 1].data)).toMatchObject(data); } - it('should not allow posting a comment on a closed post', async () => { + it('should not allow posting a reply on a closed post', async () => { axiosMock.reset(); - await mockAxiosReturnPagedComments(closedPostId, true); + await mockAxiosReturnPagedComments(closedPostId, ThreadType.QUESTION); await waitFor(() => renderComponent(closedPostId, true)); const comments = await waitFor(() => screen.findAllByTestId('comment-comment-4')); const hoverCard = within(comments[0]).getByTestId('hover-card-comment-4'); @@ -288,7 +291,7 @@ describe('ThreadView', () => { expect(screen.queryByTestId('tinymce-editor')).not.toBeInTheDocument(); }); - it('should allow posting a response', async () => { + it('should allow posting a comment', async () => { await waitFor(() => renderComponent(discussionPostId)); const post = await screen.findByTestId('post-thread-1'); @@ -540,8 +543,11 @@ describe('ThreadView', () => { // Wait for the content to load const comment = await waitFor(() => screen.findByTestId('comment-comment-1')); const hoverCard = within(comment).getByTestId('hover-card-comment-1'); + + const endorseButton = await waitFor(() => within(hoverCard).getByRole('button', { name: /Endorse/i })); + await act(async () => { - fireEvent.click(within(hoverCard).getByRole('button', { name: /Endorse/i })); + fireEvent.click(endorseButton); }); expect(axiosMock.history.patch).toHaveLength(2); expect(JSON.parse(axiosMock.history.patch[1].data)).toMatchObject({ endorsed: true }); @@ -591,7 +597,7 @@ describe('ThreadView', () => { it('pressing load more button will load next page of comments', async () => { await waitFor(() => renderComponent(discussionPostId)); - await mockAxiosReturnPagedComments(discussionPostId, false, 2); + await mockAxiosReturnPagedComments(discussionPostId, ThreadType.DISCUSSION, 2); const loadMoreButton = await findLoadMoreCommentsButton(); await act(async () => { @@ -604,7 +610,7 @@ describe('ThreadView', () => { it('newly loaded comments are appended to the old ones', async () => { await waitFor(() => renderComponent(discussionPostId)); - await mockAxiosReturnPagedComments(discussionPostId, false, 2); + await mockAxiosReturnPagedComments(discussionPostId, ThreadType.DISCUSSION, 2); const loadMoreButton = await findLoadMoreCommentsButton(); await act(async () => { @@ -622,7 +628,7 @@ describe('ThreadView', () => { const findLoadMoreCommentsButtons = () => screen.findByTestId('load-more-comments'); it('initially loads only the first page', async () => { - await mockAxiosReturnPagedComments(questionPostId); + await mockAxiosReturnPagedComments(questionPostId, ThreadType.QUESTION); act(() => renderComponent(questionPostId)); expect(await screen.findByTestId('comment-comment-4')) @@ -633,7 +639,7 @@ describe('ThreadView', () => { }); it('pressing load more button will load next page of comments', async () => { - await mockAxiosReturnPagedComments(questionPostId); + await mockAxiosReturnPagedComments(questionPostId, ThreadType.QUESTION); await waitFor(() => renderComponent(questionPostId)); const loadMoreButton = await findLoadMoreCommentsButtons(); @@ -644,7 +650,7 @@ describe('ThreadView', () => { expect(await screen.queryByTestId('comment-comment-5')) .not .toBeInTheDocument(); - await mockAxiosReturnPagedComments(questionPostId, false, 2, 1); + await mockAxiosReturnPagedComments(questionPostId, ThreadType.QUESTION, 2, 1); await act(async () => { fireEvent.click(loadMoreButton); }); @@ -664,7 +670,7 @@ describe('ThreadView', () => { expect(screen.queryByTestId('reply-comment-3')).not.toBeInTheDocument(); }); - it('pressing load more button will load next page of responses', async () => { + it('pressing load more button will load next page of replies', async () => { await waitFor(() => renderComponent(discussionPostId)); const loadMoreButton = await findLoadMoreCommentsResponsesButton(); @@ -674,7 +680,7 @@ describe('ThreadView', () => { await screen.findByTestId('reply-comment-3'); }); - it('newly loaded responses are appended to the old ones', async () => { + it('newly loaded replies are appended to the old ones', async () => { await waitFor(() => renderComponent(discussionPostId)); const loadMoreButton = await findLoadMoreCommentsResponsesButton(); @@ -687,7 +693,7 @@ describe('ThreadView', () => { expect(screen.queryByTestId('reply-comment-2')).toBeInTheDocument(); }); - it('load more button is hidden when no more responses pages to load', async () => { + it('load more button is hidden when no more replies pages to load', async () => { await waitFor(() => renderComponent(discussionPostId)); const loadMoreButton = await findLoadMoreCommentsResponsesButton(); diff --git a/src/discussions/post-comments/comments/CommentsView.jsx b/src/discussions/post-comments/comments/CommentsView.jsx index 30ee8c68..b8cd3afb 100644 --- a/src/discussions/post-comments/comments/CommentsView.jsx +++ b/src/discussions/post-comments/comments/CommentsView.jsx @@ -5,7 +5,7 @@ import { Button, Spinner } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { EndorsementStatus } from '../../../data/constants'; +import { ThreadType } from '../../../data/constants'; import { useUserPostingEnabled } from '../../data/hooks'; import { isLastElementOfList } from '../../utils'; import { usePostComments } from '../data/hooks'; @@ -13,7 +13,7 @@ import messages from '../messages'; import PostCommentsContext from '../postCommentsContext'; import { Comment, ResponseEditor } from './comment'; -const CommentsView = ({ endorsed }) => { +const CommentsView = ({ threadType }) => { const intl = useIntl(); const [addingResponse, setAddingResponse] = useState(false); const { isClosed } = useContext(PostCommentsContext); @@ -25,7 +25,7 @@ const CommentsView = ({ endorsed }) => { hasMorePages, isLoading, handleLoadMoreResponses, - } = usePostComments(endorsed); + } = usePostComments(threadType); const handleAddResponse = useCallback(() => { setAddingResponse(true); @@ -45,7 +45,7 @@ const CommentsView = ({ endorsed }) => { ), []); - const handleComments = useCallback((postCommentsIds, showLoadMoreResponses = false) => ( + const handleComments = useCallback((postCommentsIds) => (
{postCommentsIds.map((commentId) => ( { marginBottom={isLastElementOfList(postCommentsIds, commentId)} /> ))} - {hasMorePages && !isLoading && !showLoadMoreResponses && ( - - )} - {isLoading && !showLoadMoreResponses && ( -
- -
- )}
), [hasMorePages, isLoading, handleLoadMoreResponses]); return ( ((hasMorePages && isLoading) || !isLoading) && ( + <> + {endorsedCommentsIds.length > 0 && ( <> - {endorsedCommentsIds.length > 0 && ( - <> - {handleDefinition(messages.endorsedResponseCount, endorsedCommentsIds.length)} - {endorsed === EndorsementStatus.DISCUSSION - ? handleComments(endorsedCommentsIds, true) - : handleComments(endorsedCommentsIds, false)} - - )} - {endorsed !== EndorsementStatus.ENDORSED && ( - <> - {handleDefinition(messages.responseCount, unEndorsedCommentsIds.length)} - {unEndorsedCommentsIds.length === 0 &&
} - {handleComments(unEndorsedCommentsIds, false)} - {(isUserPrivilegedInPostingRestriction && !!unEndorsedCommentsIds.length && !isClosed) && ( -
- {!addingResponse && ( - - )} - -
- )} - - )} + {handleDefinition(messages.endorsedResponseCount, endorsedCommentsIds.length)} + {handleComments(endorsedCommentsIds)} + )} + {handleDefinition(messages.responseCount, unEndorsedCommentsIds.length)} + {unEndorsedCommentsIds.length > 0 && handleComments(unEndorsedCommentsIds)} + {hasMorePages && !isLoading && (!!unEndorsedCommentsIds.length || !!endorsedCommentsIds.length) && ( + + )} + {isLoading && ( +
+ +
+ )} + {(isUserPrivilegedInPostingRestriction && (!!unEndorsedCommentsIds.length || !!endorsedCommentsIds.length) + && !isClosed) && ( +
+ {!addingResponse && ( + + )} + +
+ )} + ) ); }; CommentsView.propTypes = { - endorsed: PropTypes.oneOf([ - EndorsementStatus.ENDORSED, EndorsementStatus.UNENDORSED, EndorsementStatus.DISCUSSION, + threadType: PropTypes.oneOf([ + ThreadType.DISCUSSION, ThreadType.QUESTION, ]).isRequired, }; diff --git a/src/discussions/post-comments/comments/comment/Comment.jsx b/src/discussions/post-comments/comments/comment/Comment.jsx index 02b88623..c2c5a879 100644 --- a/src/discussions/post-comments/comments/comment/Comment.jsx +++ b/src/discussions/post-comments/comments/comment/Comment.jsx @@ -82,7 +82,7 @@ const Comment = ({ }, []); const handleCommentEndorse = useCallback(async () => { - await dispatch(editComment(id, { endorsed: !endorsed }, ContentActions.ENDORSE)); + await dispatch(editComment(id, { endorsed: !endorsed })); await dispatch(fetchThread(threadId, courseId)); }, [id, endorsed, threadId]); diff --git a/src/discussions/post-comments/comments/comment/Reply.jsx b/src/discussions/post-comments/comments/comment/Reply.jsx index 5a8d1ed6..c731213c 100644 --- a/src/discussions/post-comments/comments/comment/Reply.jsx +++ b/src/discussions/post-comments/comments/comment/Reply.jsx @@ -54,7 +54,7 @@ const Reply = ({ responseId }) => { }, []); const handleReplyEndorse = useCallback(() => { - dispatch(editComment(id, { endorsed: !endorsed }, ContentActions.ENDORSE)); + dispatch(editComment(id, { endorsed: !endorsed })); }, [endorsed, id]); const handleAbusedFlag = useCallback(() => { diff --git a/src/discussions/post-comments/data/__factories__/comments.factory.js b/src/discussions/post-comments/data/__factories__/comments.factory.js index 939bf140..08d30baa 100644 --- a/src/discussions/post-comments/data/__factories__/comments.factory.js +++ b/src/discussions/post-comments/data/__factories__/comments.factory.js @@ -6,7 +6,7 @@ Factory.define('comment') .sequence('rendered_body', ['endorsed'], (idx, endorsed) => `Some contents for ${endorsed ? 'endorsed ' : 'unendorsed '}comment number ${idx}.`) .attr('thread_id', null, 'test-thread') .option('endorsedBy', null, null) - .attr('endorsed', ['endorsedBy'], (endorsedBy) => !!endorsedBy) + .attr('endorsed', ['endorsed'], (endorsed) => endorsed) .attr('endorsed_by', ['endorsedBy'], (endorsedBy) => endorsedBy) .attr('endorsed_by_label', ['endorsedBy'], (endorsedBy) => (endorsedBy ? 'Staff' : null)) .attr('endorsed_at', ['endorsedBy'], (endorsedBy) => (endorsedBy ? (new Date()).toISOString() : null)) @@ -38,7 +38,7 @@ Factory.define('commentsResult') .option('pageSize', null, 5) .option('threadId', null, 'test-thread') .option('parentId', null, null) - .option('endorsed', null, null) + .option('endorsed', false, false) .option('childCount', null, 0) .attr('pagination', ['threadId', 'count', 'page', 'pageSize'], (threadId, count, page, pageSize) => { const numPages = Math.ceil(count / pageSize); diff --git a/src/discussions/post-comments/data/api.js b/src/discussions/post-comments/data/api.js index bf73f95c..58c4648a 100644 --- a/src/discussions/post-comments/data/api.js +++ b/src/discussions/post-comments/data/api.js @@ -1,7 +1,7 @@ import { ensureConfig, getConfig, snakeCaseObject } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { EndorsementValue } from '../../../data/constants'; +import { ThreadType } from '../../../data/constants'; ensureConfig([ 'LMS_BASE_URL', @@ -20,7 +20,7 @@ export const getCommentsApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussi * @returns {Promise<{}>} */ export const getThreadComments = async (threadId, { - endorsed, + threadType, page, pageSize, reverseOrder, @@ -29,12 +29,12 @@ export const getThreadComments = async (threadId, { } = {}) => { const params = snakeCaseObject({ threadId, - endorsed: EndorsementValue[endorsed], page, pageSize, reverseOrder, requestedFields: 'profile_image', enableInContextSidebar, + mergeQuestionTypeResponses: threadType === ThreadType.QUESTION ? true : null, }); const { data } = await getAuthenticatedHttpClient().get(getCommentsApiUrl(), { params: { ...params, signal } }); diff --git a/src/discussions/post-comments/data/hooks.js b/src/discussions/post-comments/data/hooks.js index f697210e..d53c3884 100644 --- a/src/discussions/post-comments/data/hooks.js +++ b/src/discussions/post-comments/data/hooks.js @@ -40,13 +40,13 @@ export function usePost(postId) { return thread || {}; } -export function usePostComments(endorsed = null) { +export function usePostComments(threadType) { const { enableInContextSidebar, postId } = useContext(DiscussionContext); const [isLoading, dispatch] = useDispatchWithState(); - const comments = useSelector(selectThreadComments(postId, endorsed)); + const comments = useSelector(selectThreadComments(postId)); const reverseOrder = useSelector(selectCommentSortOrder); - const hasMorePages = useSelector(selectThreadHasMorePages(postId, endorsed)); - const currentPage = useSelector(selectThreadCurrentPage(postId, endorsed)); + const hasMorePages = useSelector(selectThreadHasMorePages(postId)); + const currentPage = useSelector(selectThreadCurrentPage(postId)); const endorsedCommentsIds = useMemo(() => ( [...filterPosts(comments, 'endorsed')].map(comment => comment.id) @@ -58,19 +58,19 @@ export function usePostComments(endorsed = null) { const handleLoadMoreResponses = useCallback(async () => { const params = { - endorsed, + threadType, page: currentPage + 1, reverseOrder, }; await dispatch(fetchThreadComments(postId, params)); trackLoadMoreEvent(postId, params); - }, [currentPage, endorsed, postId, reverseOrder]); + }, [currentPage, threadType, postId, reverseOrder]); useEffect(() => { const abortController = new AbortController(); dispatch(fetchThreadComments(postId, { - endorsed, + threadType, page: 1, reverseOrder, enableInContextSidebar, @@ -80,7 +80,7 @@ export function usePostComments(endorsed = null) { return () => { abortController.abort(); }; - }, [postId, endorsed, reverseOrder, enableInContextSidebar]); + }, [postId, threadType, reverseOrder, enableInContextSidebar]); return { endorsedCommentsIds, diff --git a/src/discussions/post-comments/data/redux.test.js b/src/discussions/post-comments/data/redux.test.js index dbad7cf3..2e419942 100644 --- a/src/discussions/post-comments/data/redux.test.js +++ b/src/discussions/post-comments/data/redux.test.js @@ -4,7 +4,7 @@ import { Factory } from 'rosie'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { initializeMockApp } from '@edx/frontend-platform/testing'; -import { EndorsementStatus } from '../../../data/constants'; +import { ThreadType } from '../../../data/constants'; import { initializeStore } from '../../../store'; import executeThunk from '../../../test-utils'; import { getCommentsApiUrl } from './api'; @@ -39,37 +39,23 @@ describe('Comments/Responses data layer tests', () => { }); test.each([ - { - threadType: 'discussion', - endorsed: EndorsementStatus.DISCUSSION, - }, - { - threadType: 'question', - endorsed: EndorsementStatus.UNENDORSED, - }, - { - threadType: 'question', - endorsed: EndorsementStatus.ENDORSED, - }, - ])('successfully processes comments for \'$threadType\' thread with endorsed=$endorsed', async ({ - endorsed, - }) => { + ThreadType.DISCUSSION, + ThreadType.QUESTION, + ])('successfully processes comments for %s type thread', async (threadType) => { const threadId = 'test-thread'; axiosMock.onGet(commentsApiUrl) .reply(200, Factory.build('commentsResult')); - await executeThunk(fetchThreadComments(threadId, { endorsed }), store.dispatch, store.getState); + await executeThunk(fetchThreadComments(threadId, { threadType }), store.dispatch, store.getState); expect(store.getState().comments.commentsInThreads) - .toEqual({ 'test-thread': { [endorsed]: ['comment-1', 'comment-2', 'comment-3'] } }); + .toEqual({ 'test-thread': ['comment-1', 'comment-2', 'comment-3'] }); expect(store.getState().comments.pagination) .toEqual({ 'test-thread': { - [endorsed]: { - currentPage: 1, - totalPages: 1, - hasMorePages: false, - }, + currentPage: 1, + totalPages: 1, + hasMorePages: false, }, }); expect(Object.keys(store.getState().comments.commentsById)) @@ -82,7 +68,7 @@ describe('Comments/Responses data layer tests', () => { .toEqual('test-thread'); }); - test('successfully processes comment responses', async () => { + test('successfully processes comment replies', async () => { const threadId = 'test-thread'; const commentId = 'comment-1'; axiosMock.onGet(commentsApiUrl) @@ -101,7 +87,7 @@ describe('Comments/Responses data layer tests', () => { .toEqual({ 'comment-1': ['comment-4', 'comment-5', 'comment-6'] }); }); - test('successfully handles response creation for discussion type threads', async () => { + test('successfully handles comment creation for threads', async () => { const threadId = 'test-thread'; const content = 'Test comment'; axiosMock.onGet(commentsApiUrl) @@ -119,21 +105,19 @@ describe('Comments/Responses data layer tests', () => { await executeThunk(addComment(content, threadId, null), store.dispatch, store.getState); expect(store.getState().comments.commentsInThreads[threadId]) - .toEqual({ - [EndorsementStatus.DISCUSSION]: [ - 'comment-1', - 'comment-2', - 'comment-3', - 'comment-4', - ], - }); + .toEqual([ + 'comment-1', + 'comment-2', + 'comment-3', + 'comment-4', + ]); expect(Object.keys(store.getState().comments.commentsById)) .toEqual(['comment-1', 'comment-2', 'comment-3', 'comment-4']); expect(store.getState().comments.commentsById['comment-4'].threadId) .toEqual(threadId); }); - test('successfully handles reply creation for discussion type threads', async () => { + test('successfully handles reply creation for threads', async () => { const threadId = 'test-thread'; const parentId = 'comment-1'; const content = 'Test comment'; @@ -156,13 +140,11 @@ describe('Comments/Responses data layer tests', () => { await executeThunk(addComment(content, threadId, null), store.dispatch, store.getState); expect(store.getState().comments.commentsInThreads[threadId]) - .toEqual({ - [EndorsementStatus.DISCUSSION]: [ - 'comment-1', - 'comment-2', - 'comment-3', - ], - }); + .toEqual([ + 'comment-1', + 'comment-2', + 'comment-3', + ]); expect(Object.keys(store.getState().comments.commentsById)) .toEqual(['comment-1', 'comment-2', 'comment-3', 'comment-4']); expect(store.getState().comments.commentsInComments[parentId]) @@ -173,54 +155,6 @@ describe('Comments/Responses data layer tests', () => { .toEqual(parentId); }); - test('successfully handles comment creation for question type threads', async () => { - const threadId = 'test-thread'; - const content = 'Test comment'; - axiosMock.onGet(commentsApiUrl) - .reply(200, Factory.build('commentsResult', null, { endorsed: false })); - await executeThunk( - fetchThreadComments(threadId, { endorsed: EndorsementStatus.UNENDORSED }), - store.dispatch, - store.getState, - ); - axiosMock.onGet(commentsApiUrl) - .reply(200, Factory.build('commentsResult', null, { endorsed: true })); - await executeThunk( - fetchThreadComments(threadId, { endorsed: EndorsementStatus.ENDORSED }), - store.dispatch, - store.getState, - ); - - axiosMock.onPost(commentsApiUrl) - .reply(200, Factory.build('comment', { - thread_id: threadId, - raw_body: content, - rendered_body: content, - })); - - await executeThunk(addComment(content, threadId, null), store.dispatch, store.getState); - - expect(store.getState().comments.commentsInThreads[threadId]) - .toEqual({ - [EndorsementStatus.UNENDORSED]: [ - 'comment-1', - 'comment-2', - 'comment-3', - // Newly-added comment - 'comment-7', - ], - [EndorsementStatus.ENDORSED]: [ - 'comment-4', - 'comment-5', - 'comment-6', - ], - }); - expect(Object.keys(store.getState().comments.commentsById)) - .toEqual(['comment-1', 'comment-2', 'comment-3', 'comment-4', 'comment-5', 'comment-6', 'comment-7']); - expect(store.getState().comments.commentsById['comment-7'].threadId) - .toEqual(threadId); - }); - test('successfully handles comment edits', async () => { const threadId = 'test-thread'; const commentId = 'comment-1'; @@ -271,7 +205,7 @@ describe('Comments/Responses data layer tests', () => { .toContain(commentId); }); - test('correctly handles comment responses pagination after posting a new response', async () => { + test('correctly handles comment replies pagination after posting a new reply', async () => { const threadId = 'test-thread'; const commentId = 'comment-1'; @@ -327,15 +261,9 @@ describe('Comments/Responses data layer tests', () => { }); test.each([ - { - threadType: 'discussion', - endorsed: EndorsementStatus.DISCUSSION, - }, - { - threadType: 'unendorsed', - endorsed: EndorsementStatus.UNENDORSED, - }, - ])('correctly handles `$threadType` thread comments pagination after posting a new comment', async ({ endorsed }) => { + ThreadType.DISCUSSION, + ThreadType.QUESTION, + ])('correctly handles %s thread comments pagination after posting a new comment', async (threadType) => { const threadId = 'test-thread'; // Build all comments first, so we can paginate over them and they @@ -348,7 +276,7 @@ describe('Comments/Responses data layer tests', () => { results: allComments.slice(0, 3), pagination: { count: 4, numPages: 2 }, }); - await executeThunk(fetchThreadComments(threadId, { endorsed }), store.dispatch, store.getState); + await executeThunk(fetchThreadComments(threadId, { threadType }), store.dispatch, store.getState); // Post new comment const comment = Factory.build('comment', { thread_id: threadId }); @@ -365,10 +293,10 @@ describe('Comments/Responses data layer tests', () => { results: allComments.slice(3, 6), pagination: { count: 6, numPages: 2 }, }); - await executeThunk(fetchThreadComments(threadId, { page: 2, endorsed }), store.dispatch, store.getState); + await executeThunk(fetchThreadComments(threadId, { page: 2, threadType }), store.dispatch, store.getState); // sorting is implemented on backend - expect(store.getState().comments.commentsInThreads[threadId][endorsed]) + expect(store.getState().comments.commentsInThreads[threadId]) .toEqual([ 'comment-1', 'comment-2', diff --git a/src/discussions/post-comments/data/selectors.js b/src/discussions/post-comments/data/selectors.js index fb04b8f3..53869046 100644 --- a/src/discussions/post-comments/data/selectors.js +++ b/src/discussions/post-comments/data/selectors.js @@ -8,9 +8,9 @@ export const selectCommentOrResponseById = commentOrResponseId => createSelector comments => comments[commentOrResponseId], ); -export const selectThreadComments = (threadId, endorsed = null) => createSelector( +export const selectThreadComments = (threadId) => createSelector( [ - state => state.comments.commentsInThreads[threadId]?.[endorsed] || [], + state => state.comments.commentsInThreads[threadId] || [], selectCommentsById, ], mapIdToComment, @@ -28,12 +28,12 @@ export const selectCommentResponses = commentId => createSelector( mapIdToComment, ); -export const selectThreadHasMorePages = (threadId, endorsed = null) => ( - state => state.comments.pagination[threadId]?.[endorsed]?.hasMorePages || false +export const selectThreadHasMorePages = (threadId) => ( + state => state.comments.pagination[threadId]?.hasMorePages || false ); -export const selectThreadCurrentPage = (threadId, endorsed = null) => ( - state => state.comments.pagination[threadId]?.[endorsed]?.currentPage || null +export const selectThreadCurrentPage = (threadId) => ( + state => state.comments.pagination[threadId]?.currentPage || null ); export const selectCommentHasMorePages = commentId => ( diff --git a/src/discussions/post-comments/data/slices.js b/src/discussions/post-comments/data/slices.js index 96ebf1f9..ca471541 100644 --- a/src/discussions/post-comments/data/slices.js +++ b/src/discussions/post-comments/data/slices.js @@ -1,6 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; -import { EndorsementStatus, RequestStatus } from '../../../data/constants'; +import { RequestStatus } from '../../../data/constants'; const commentsSlice = createSlice({ name: 'comments', @@ -31,15 +31,14 @@ const commentsSlice = createSlice({ } ), fetchCommentsSuccess: (state, { payload }) => { - const { threadId, page, endorsed } = payload; + const { threadId, page } = payload; const newState = { ...state }; newState.status = RequestStatus.SUCCESSFUL; - newState.commentsInThreads = { ...newState.commentsInThreads, - [threadId]: newState.commentsInThreads[threadId] || {}, + [threadId]: newState.commentsInThreads[threadId] || [], }; newState.pagination = { @@ -50,23 +49,16 @@ const commentsSlice = createSlice({ if (page === 1) { newState.commentsInThreads = { ...newState.commentsInThreads, - [threadId]: { - ...newState.commentsInThreads[threadId], - [endorsed]: payload.commentsInThreads[threadId] || [], - }, + [threadId]: [...payload.commentsInThreads[threadId]] || [], }; } else { newState.commentsInThreads = { - ...newState.commentsInThreads, - [threadId]: { - ...newState.commentsInThreads[threadId], - [endorsed]: [ - ...new Set([ - ...(newState.commentsInThreads[threadId][endorsed] || []), - ...(payload.commentsInThreads[threadId] || []), - ]), - ], - }, + [threadId]: [ + ...new Set([ + ...(newState.commentsInThreads[threadId] || []), + ...(payload.commentsInThreads[threadId] || []), + ]), + ], }; } @@ -74,11 +66,9 @@ const commentsSlice = createSlice({ ...newState.pagination, [threadId]: { ...newState.pagination[threadId], - [endorsed]: { - currentPage: payload.page, - totalPages: payload.pagination.numPages, - hasMorePages: Boolean(payload.pagination.next), - }, + currentPage: payload.page, + totalPages: payload.pagination.numPages, + hasMorePages: Boolean(payload.pagination.next), }, }; @@ -181,21 +171,10 @@ const commentsSlice = createSlice({ ], }; } else { - const threadComments = newState.commentsInThreads[payload.threadId] || {}; - const endorsementStatus = threadComments[EndorsementStatus.DISCUSSION] - ? EndorsementStatus.DISCUSSION - : EndorsementStatus.UNENDORSED; - - const updatedThreadComments = { - ...threadComments, - [endorsementStatus]: [ - ...(threadComments[endorsementStatus] || []), - payload.id, - ], - }; + const threadComments = newState.commentsInThreads[payload.threadId] || []; newState.commentsInThreads = { ...newState.commentsInThreads, - [payload.threadId]: updatedThreadComments, + [payload.threadId]: [...threadComments, payload.id], }; } @@ -231,30 +210,7 @@ const commentsSlice = createSlice({ [payload.id]: payload, }, commentDraft: null, - } - ), - updateCommentsList: (state, { payload }) => { - const { id: commentId, threadId, endorsed } = payload; - const commentAddListtype = endorsed ? EndorsementStatus.ENDORSED : EndorsementStatus.UNENDORSED; - const commentRemoveListType = !endorsed ? EndorsementStatus.ENDORSED : EndorsementStatus.UNENDORSED; - - const updatedThread = { ...state.commentsInThreads[threadId] }; - - updatedThread[commentRemoveListType] = updatedThread[commentRemoveListType] - ?.filter(item => item !== commentId) - ?? []; - updatedThread[commentAddListtype] = [ - ...(updatedThread[commentAddListtype] || []), commentId, - ]; - - return { - ...state, - commentsInThreads: { - ...state.commentsInThreads, - [threadId]: updatedThread, - }, - }; - }, + }), deleteCommentRequest: (state) => ( { ...state, @@ -285,12 +241,9 @@ const commentsSlice = createSlice({ commentsById: { ...state.commentsById }, }; - [EndorsementStatus.DISCUSSION, EndorsementStatus.UNENDORSED, EndorsementStatus.ENDORSED].forEach((endorsed) => { - newState.commentsInThreads[threadId] = { - ...newState.commentsInThreads[threadId], - [endorsed]: newState.commentsInThreads[threadId]?.[endorsed]?.filter(item => item !== commentId), - }; - }); + newState.commentsInThreads[threadId] = [ + ...newState.commentsInThreads[threadId]?.filter(item => item !== commentId) || [], + ]; if (parentId) { newState.commentsInComments[parentId] = newState.commentsInComments[parentId].filter( @@ -328,7 +281,6 @@ export const { updateCommentFailed, updateCommentRequest, updateCommentSuccess, - updateCommentsList, deleteCommentDenied, deleteCommentFailed, deleteCommentRequest, diff --git a/src/discussions/post-comments/data/thunks.js b/src/discussions/post-comments/data/thunks.js index 311774a6..f7480153 100644 --- a/src/discussions/post-comments/data/thunks.js +++ b/src/discussions/post-comments/data/thunks.js @@ -1,7 +1,6 @@ import { camelCaseObject } from '@edx/frontend-platform'; import { logError } from '@edx/frontend-platform/logging'; -import { ContentActions, EndorsementStatus } from '../../../data/constants'; import { getHttpErrorStatus } from '../../utils'; import { deleteComment, getCommentResponses, getThreadComments, postComment, updateComment, @@ -26,7 +25,6 @@ import { updateCommentDenied, updateCommentFailed, updateCommentRequest, - updateCommentsList, updateCommentSuccess, } from './slices'; @@ -78,7 +76,7 @@ export function fetchThreadComments( { page = 1, reverseOrder, - endorsed = EndorsementStatus.DISCUSSION, + threadType, enableInContextSidebar, signal, } = {}, @@ -87,11 +85,10 @@ export function fetchThreadComments( try { dispatch(fetchCommentsRequest()); const data = await getThreadComments(threadId, { - page, reverseOrder, endorsed, enableInContextSidebar, signal, + page, reverseOrder, threadType, enableInContextSidebar, signal, }); dispatch(fetchCommentsSuccess({ ...normaliseComments(camelCaseObject(data)), - endorsed, page, threadId, })); @@ -127,15 +124,12 @@ export function fetchCommentResponses(commentId, { page = 1, reverseOrder = true }; } -export function editComment(commentId, comment, action = null) { +export function editComment(commentId, comment) { return async (dispatch) => { try { dispatch(updateCommentRequest({ commentId })); const data = await updateComment(commentId, comment); dispatch(updateCommentSuccess(camelCaseObject(data))); - if (action === ContentActions.ENDORSE) { - dispatch(updateCommentsList(camelCaseObject(data))); - } } catch (error) { if (getHttpErrorStatus(error) === 403) { dispatch(updateCommentDenied()); diff --git a/src/index.scss b/src/index.scss index e4323d24..e80990b4 100755 --- a/src/index.scss +++ b/src/index.scss @@ -61,10 +61,6 @@ $fa-font-path: "~font-awesome/fonts"; font-weight: 500 !important; } -.font-style-normal { - font-style: normal !important; -} - .post-footer-icon-dimensions { width: 32px !important; height: 32px !important; @@ -496,7 +492,7 @@ code { } .font-style { - font-style: normal; + font-style: normal !important; } .in-context-navbar {