diff --git a/client/src/components/CommentApp/actions/comments.ts b/client/src/components/CommentApp/actions/comments.ts index 353979b0bb..565f15c969 100644 --- a/client/src/components/CommentApp/actions/comments.ts +++ b/client/src/components/CommentApp/actions/comments.ts @@ -8,6 +8,7 @@ import type { export const ADD_COMMENT = 'add-comment'; export const UPDATE_COMMENT = 'update-comment'; export const DELETE_COMMENT = 'delete-comment'; +export const RESOLVE_COMMENT = 'resolve-comment'; export const SET_FOCUSED_COMMENT = 'set-focused-comment'; export const SET_PINNED_COMMENT = 'set-pinned-comment'; @@ -33,6 +34,11 @@ export interface DeleteCommentAction { commentId: number; } +export interface ResolveCommentAction { + type: typeof RESOLVE_COMMENT; + commentId: number; +} + export interface SetFocusedCommentAction { type: typeof SET_FOCUSED_COMMENT; commentId: number | null; @@ -67,6 +73,7 @@ export type Action = | AddCommentAction | UpdateCommentAction | DeleteCommentAction + | ResolveCommentAction | SetFocusedCommentAction | AddReplyAction | UpdateReplyAction @@ -98,6 +105,14 @@ export function deleteComment(commentId: number): DeleteCommentAction { }; } +export function resolveComment(commentId: number): ResolveCommentAction { + return { + type: RESOLVE_COMMENT, + commentId, + }; +} + + export function setFocusedComment( commentId: number | null, { updatePinnedComment } = { updatePinnedComment: false } @@ -155,6 +170,7 @@ export const commentActionFunctions = { addComment, updateComment, deleteComment, + resolveComment, setFocusedComment, addReply, updateReply, diff --git a/client/src/components/CommentApp/components/Comment/index.tsx b/client/src/components/CommentApp/components/Comment/index.tsx index 0306e5ebb2..931eb23eb9 100644 --- a/client/src/components/CommentApp/components/Comment/index.tsx +++ b/client/src/components/CommentApp/components/Comment/index.tsx @@ -6,6 +6,7 @@ import { Author, Comment, newCommentReply } from '../../state/comments'; import { updateComment, deleteComment, + resolveComment, setFocusedComment, addReply } from '../../actions/comments'; @@ -64,11 +65,9 @@ async function doDeleteComment(comment: Comment, store: Store) { } } -function resolveComment(comment: Comment, store: Store) { +function doResolveComment(comment: Comment, store: Store) { store.dispatch( - updateComment(comment.localId, { - resolved: true, - }) + resolveComment(comment.localId) ); } @@ -482,7 +481,7 @@ export default class CommentComponent extends React.Component { commentReply={comment} store={store} strings={strings} - onResolve={resolveComment} + onResolve={doResolveComment} onEdit={onEdit} onDelete={onDelete} /> diff --git a/client/src/components/CommentApp/state/comments.test.ts b/client/src/components/CommentApp/state/comments.test.ts index f03a4d9699..f7c045b55c 100644 --- a/client/src/components/CommentApp/state/comments.test.ts +++ b/client/src/components/CommentApp/state/comments.test.ts @@ -92,6 +92,13 @@ test('Local comment deleted', () => { expect(newState.comments.has(4)).toBe(false); }); +test('Local comment resolved', () => { + // Test that resolving a comment without a remoteId removes it from the state entirely + const resolveAction = actions.resolveComment(4); + const newState = reducer(basicCommentsState, resolveAction); + expect(newState.comments.has(4)).toBe(false); +}); + test('Remote comment deleted', () => { // Test that deleting a comment without a remoteId does not remove it from the state, but marks it as deleted const deleteAction = actions.deleteComment(1); @@ -108,6 +115,22 @@ test('Remote comment deleted', () => { ); }); +test('Remote comment resolved', () => { + // Test that resolving a comment without a remoteId does not remove it from the state, but marks it as resolved + const resolveAction = actions.resolveComment(1); + const newState = reducer(basicCommentsState, resolveAction); + const comment = newState.comments.get(1); + expect(comment).toBeDefined(); + if (comment) { + expect(comment.resolved).toBe(true); + } + expect(newState.focusedComment).toBe(null); + expect(newState.pinnedComment).toBe(null); + expect(newState.remoteCommentCount).toBe( + basicCommentsState.remoteCommentCount + ); +}); + test('Comment focused', () => { const focusAction = actions.setFocusedComment(4); const newState = reducer(basicCommentsState, focusAction); diff --git a/client/src/components/CommentApp/state/comments.ts b/client/src/components/CommentApp/state/comments.ts index 1be584958b..3c5b49a25d 100644 --- a/client/src/components/CommentApp/state/comments.ts +++ b/client/src/components/CommentApp/state/comments.ts @@ -191,6 +191,22 @@ export const reducer = produce((draft: CommentsState, action: actions.Action) => } }; + const resolveComment = (comment: Comment) => { + if (!comment.remoteId) { + // If the comment doesn't exist in the database, there's no need to keep it around locally + draft.comments.delete(comment.localId); + } else { + comment.resolved = true; + } + // Unset focusedComment if the focused comment is the one being resolved + if (draft.focusedComment === comment.localId) { + draft.focusedComment = null; + } + if (draft.pinnedComment === comment.localId) { + draft.pinnedComment = null; + } + }; + switch (action.type) { case actions.ADD_COMMENT: { draft.comments.set(action.comment.localId, action.comment); @@ -215,6 +231,15 @@ export const reducer = produce((draft: CommentsState, action: actions.Action) => deleteComment(comment); break; } + case actions.RESOLVE_COMMENT: { + const comment = draft.comments.get(action.commentId); + if (!comment) { + break; + } + + resolveComment(comment); + break; + } case actions.SET_FOCUSED_COMMENT: { if ((action.commentId === null) || (draft.comments.has(action.commentId))) { draft.focusedComment = action.commentId; @@ -270,7 +295,7 @@ export const reducer = produce((draft: CommentsState, action: actions.Action) => const comments = draft.comments; for (const comment of comments.values()) { if (comment.contentpath.startsWith(action.contentPath)) { - deleteComment(comment); + resolveComment(comment); } } break;