diff --git a/webapp/channels/src/actions/post_actions.test.ts b/webapp/channels/src/actions/post_actions.test.ts index 801da3dad8..452ecb2720 100644 --- a/webapp/channels/src/actions/post_actions.test.ts +++ b/webapp/channels/src/actions/post_actions.test.ts @@ -12,10 +12,12 @@ import * as Actions from 'actions/post_actions'; import mockStore from 'tests/test_store'; import {Constants, ActionTypes, RHSStates} from 'utils/constants'; +import * as PostUtils from 'utils/post_utils'; import type {GlobalState} from 'types/store'; jest.mock('mattermost-redux/actions/posts', () => ({ + removeReaction: (...args: any[]) => ({type: 'MOCK_REMOVE_REACTION', args}), addReaction: (...args: any[]) => ({type: 'MOCK_ADD_REACTION', args}), createPost: (...args: any[]) => ({type: 'MOCK_CREATE_POST', args}), createPostImmediately: (...args: any[]) => ({type: 'MOCK_CREATE_POST_IMMEDIATELY', args}), @@ -48,6 +50,8 @@ jest.mock('utils/user_agent', () => ({ isDesktopApp: jest.fn().mockReturnValue(false), })); +const mockMakeGetIsReactionAlreadyAddedToPost = jest.spyOn(PostUtils, 'makeGetIsReactionAlreadyAddedToPost'); + const POST_CREATED_TIME = Date.now(); // This mocks the Date.now() function so it returns a constant value @@ -433,6 +437,84 @@ describe('Actions.Posts', () => { }); }); + describe('submitReaction', () => { + describe('addReaction', () => { + test('should add reaction when the action is + and the reaction is not added', async () => { + const testStore = mockStore(initialState); + + mockMakeGetIsReactionAlreadyAddedToPost.mockReturnValueOnce(() => false); + + testStore.dispatch(Actions.submitReaction('post_id_1', '+', 'emoji_name_1')); + + expect(testStore.getActions()).toEqual([ + {args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_ADD_REACTION'}, + {args: ['emoji_name_1'], type: 'MOCK_ADD_RECENT_EMOJI'}, + ]); + }); + + test('should take no action when the action is + and the reaction has already been added', async () => { + const testStore = mockStore(initialState); + + mockMakeGetIsReactionAlreadyAddedToPost.mockReturnValueOnce(() => true); + + testStore.dispatch(Actions.submitReaction('post_id_1', '+', 'emoji_name_1')); + + expect(testStore.getActions()).toEqual([]); + }); + }); + + describe('removeReaction', () => { + test('should remove reaction when the action is - and the reaction has already been added', async () => { + const testStore = mockStore(initialState); + + mockMakeGetIsReactionAlreadyAddedToPost.mockReturnValueOnce(() => true); + + testStore.dispatch(Actions.submitReaction('post_id_1', '-', 'emoji_name_1')); + + expect(testStore.getActions()).toEqual([ + {args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_REMOVE_REACTION'}, + ]); + }); + + test('should take no action when the action is - and the reaction is not added', async () => { + const testStore = mockStore(initialState); + + mockMakeGetIsReactionAlreadyAddedToPost.mockReturnValueOnce(() => false); + + testStore.dispatch(Actions.submitReaction('post_id_1', '-', 'emoji_name_1')); + + expect(testStore.getActions()).toEqual([]); + }); + }); + }); + + describe('toggleReaction', () => { + test('should add reaction when the reaction is not added', async () => { + const testStore = mockStore(initialState); + + mockMakeGetIsReactionAlreadyAddedToPost.mockReturnValueOnce(() => false); + + testStore.dispatch(Actions.toggleReaction('post_id_1', 'emoji_name_1')); + + expect(testStore.getActions()).toEqual([ + {args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_ADD_REACTION'}, + {args: ['emoji_name_1'], type: 'MOCK_ADD_RECENT_EMOJI'}, + ]); + }); + + test('should remove reaction when the reaction has already been added', async () => { + const testStore = mockStore(initialState); + + mockMakeGetIsReactionAlreadyAddedToPost.mockReturnValueOnce(() => true); + + testStore.dispatch(Actions.toggleReaction('post_id_1', 'emoji_name_1')); + + expect(testStore.getActions()).toEqual([ + {args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_REMOVE_REACTION'}, + ]); + }); + }); + test('addReaction', async () => { const testStore = mockStore(initialState); diff --git a/webapp/channels/src/actions/post_actions.ts b/webapp/channels/src/actions/post_actions.ts index 08646bb578..bd00ab49f7 100644 --- a/webapp/channels/src/actions/post_actions.ts +++ b/webapp/channels/src/actions/post_actions.ts @@ -34,6 +34,7 @@ import { StoragePrefixes, } from 'utils/constants'; import {matchEmoticons} from 'utils/emoticons'; +import {makeGetIsReactionAlreadyAddedToPost} from 'utils/post_utils'; import * as UserAgent from 'utils/user_agent'; import type {GlobalState} from 'types/store'; @@ -140,6 +141,36 @@ function storeCommentDraft(rootPostId: string, draft: null) { }; } +export function submitReaction(postId: string, action: string, emojiName: string) { + return (dispatch: DispatchFunc, getState: GetStateFunc) => { + const state = getState() as GlobalState; + const getIsReactionAlreadyAddedToPost = makeGetIsReactionAlreadyAddedToPost(); + + const isReactionAlreadyAddedToPost = getIsReactionAlreadyAddedToPost(state, postId, emojiName); + + if (action === '+' && !isReactionAlreadyAddedToPost) { + dispatch(addReaction(postId, emojiName)); + } else if (action === '-' && isReactionAlreadyAddedToPost) { + dispatch(PostActions.removeReaction(postId, emojiName)); + } + return {data: true}; + }; +} + +export function toggleReaction(postId: string, emojiName: string) { + return (dispatch: DispatchFunc, getState: GetStateFunc) => { + const state = getState() as GlobalState; + const getIsReactionAlreadyAddedToPost = makeGetIsReactionAlreadyAddedToPost(); + + const isReactionAlreadyAddedToPost = getIsReactionAlreadyAddedToPost(state, postId, emojiName); + + if (isReactionAlreadyAddedToPost) { + return dispatch(PostActions.removeReaction(postId, emojiName)); + } + return dispatch(addReaction(postId, emojiName)); + }; +} + export function addReaction(postId: string, emojiName: string) { return (dispatch: DispatchFunc) => { dispatch(PostActions.addReaction(postId, emojiName)); diff --git a/webapp/channels/src/actions/views/create_comment.test.jsx b/webapp/channels/src/actions/views/create_comment.test.jsx index 82f4f96506..aeaf8c9bc3 100644 --- a/webapp/channels/src/actions/views/create_comment.test.jsx +++ b/webapp/channels/src/actions/views/create_comment.test.jsx @@ -2,7 +2,6 @@ // See LICENSE.txt for license information. import { - removeReaction, addMessageIntoHistory, moveHistoryIndexBack, } from 'mattermost-redux/actions/posts'; @@ -17,7 +16,6 @@ import { updateCommentDraft, makeOnMoveHistoryIndex, submitPost, - submitReaction, submitCommand, makeOnSubmit, makeOnEditLatestPost, @@ -63,6 +61,7 @@ jest.mock('actions/hooks', () => ({ })); jest.mock('actions/post_actions', () => ({ + submitReaction: (...args) => ({type: 'MOCK_SUBMIT_REACTION', args}), addReaction: (...args) => ({type: 'MOCK_ADD_REACTION', args}), createPost: jest.fn(() => ({type: 'MOCK_CREATE_POST'})), setEditingPost: (...args) => ({type: 'MOCK_SET_EDITING_POST', args}), @@ -293,25 +292,6 @@ describe('rhs view actions', () => { }); }); - describe('submitReaction', () => { - test('it adds a reaction when action is +', () => { - store.dispatch(submitReaction('post_id_1', '+', 'emoji_name_1')); - - const testStore = mockStore(initialState); - testStore.dispatch(PostActions.addReaction('post_id_1', 'emoji_name_1')); - expect(store.getActions()).toEqual(testStore.getActions()); - }); - - test('it removes a reaction when action is -', () => { - store.dispatch(submitReaction('post_id_1', '-', 'emoji_name_1')); - - const testStore = mockStore(initialState); - testStore.dispatch(removeReaction('post_id_1', 'emoji_name_1')); - - expect(store.getActions()).toEqual(testStore.getActions()); - }); - }); - describe('submitCommand', () => { const args = { channel_id: channelId, @@ -400,7 +380,7 @@ describe('rhs view actions', () => { })); const testStore = mockStore(initialState); - testStore.dispatch(submitReaction(latestPostId, '+', 'smile')); + testStore.dispatch(PostActions.submitReaction(latestPostId, '+', 'smile')); expect(store.getActions()).toEqual( expect.arrayContaining(testStore.getActions()), diff --git a/webapp/channels/src/actions/views/create_comment.tsx b/webapp/channels/src/actions/views/create_comment.tsx index 3fd7fe681f..bbddbe904f 100644 --- a/webapp/channels/src/actions/views/create_comment.tsx +++ b/webapp/channels/src/actions/views/create_comment.tsx @@ -4,7 +4,6 @@ import type {Post} from '@mattermost/types/posts'; import { - removeReaction, addMessageIntoHistory, moveHistoryIndexBack, moveHistoryIndexForward, @@ -106,17 +105,6 @@ export function submitPost(channelId: string, rootId: string, draft: PostDraft) }; } -export function submitReaction(postId: string, action: string, emojiName: string) { - return (dispatch: DispatchFunc) => { - if (action === '+') { - dispatch(PostActions.addReaction(postId, emojiName)); - } else if (action === '-') { - dispatch(removeReaction(postId, emojiName)); - } - return {data: true}; - }; -} - export function submitCommand(channelId: string, rootId: string, draft: PostDraft) { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); @@ -170,7 +158,7 @@ export function makeOnSubmit(channelId: string, rootId: string, latestPostId: st const emojiMap = new EmojiMap(emojis); if (isReaction && emojiMap.has(isReaction[2])) { - dispatch(submitReaction(latestPostId, isReaction[1], isReaction[2])); + dispatch(PostActions.submitReaction(latestPostId, isReaction[1], isReaction[2])); } else if (message.indexOf('/') === 0 && !options.ignoreSlash) { try { await dispatch(submitCommand(channelId, rootId, draft)); diff --git a/webapp/channels/src/components/advanced_create_post/advanced_create_post.test.tsx b/webapp/channels/src/components/advanced_create_post/advanced_create_post.test.tsx index c61493081a..760f2c5d06 100644 --- a/webapp/channels/src/components/advanced_create_post/advanced_create_post.test.tsx +++ b/webapp/channels/src/components/advanced_create_post/advanced_create_post.test.tsx @@ -76,6 +76,7 @@ const baseProp: Props = { addMessageIntoHistory: jest.fn(), moveHistoryIndexBack: jest.fn(), moveHistoryIndexForward: jest.fn(), + submitReaction: jest.fn(), addReaction: jest.fn(), removeReaction: jest.fn(), clearDraftUploads: jest.fn(), @@ -728,13 +729,13 @@ describe('components/advanced_create_post', () => { }); it('onSubmit test for addReaction message', async () => { - const addReaction = jest.fn(); + const submitReaction = jest.fn(); const wrapper = shallow( advancedCreatePost({ actions: { ...baseProp.actions, - addReaction, + submitReaction, }, }), ); @@ -744,17 +745,17 @@ describe('components/advanced_create_post', () => { }); await (wrapper.instance() as AdvancedCreatePost).handleSubmit(submitEvent); - expect(addReaction).toHaveBeenCalledWith('a', 'smile'); + expect(submitReaction).toHaveBeenCalledWith('a', '+', 'smile'); }); - it('onSubmit test for removeReaction message', () => { - const removeReaction = jest.fn(); + it('onSubmit test for removeReaction message', async () => { + const submitReaction = jest.fn(); const wrapper = shallow( advancedCreatePost({ actions: { ...baseProp.actions, - removeReaction, + submitReaction, }, }), ); @@ -763,9 +764,8 @@ describe('components/advanced_create_post', () => { message: '-:smile:', }); - const form = wrapper.find('#create_post'); - form.simulate('Submit', {preventDefault: jest.fn()}); - expect(removeReaction).toHaveBeenCalledWith('a', 'smile'); + await (wrapper.instance() as AdvancedCreatePost).handleSubmit(submitEvent); + expect(submitReaction).toHaveBeenCalledWith('a', '-', 'smile'); }); /*it('check for postError state on handlePostError callback', () => { diff --git a/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx b/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx index 7a12dc2128..8f23305787 100644 --- a/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx +++ b/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx @@ -174,6 +174,8 @@ export type Props = { // func called for navigation through messages by Down arrow moveHistoryIndexForward: (index: string) => Promise; + submitReaction: (postId: string, action: string, emojiName: string) => void; + // func called for adding a reaction addReaction: (postId: string, emojiName: string) => void; @@ -799,10 +801,8 @@ class AdvancedCreatePost extends React.PureComponent { const emojiName = isReaction[2]; const postId = this.props.latestReplyablePostId; - if (postId && action === '+') { - this.props.actions.addReaction(postId, emojiName); - } else if (postId && action === '-') { - this.props.actions.removeReaction(postId, emojiName); + if (postId) { + this.props.actions.submitReaction(postId, action, emojiName); } this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, null, channelId); diff --git a/webapp/channels/src/components/advanced_create_post/index.ts b/webapp/channels/src/components/advanced_create_post/index.ts index 4738be862b..a070b53e39 100644 --- a/webapp/channels/src/components/advanced_create_post/index.ts +++ b/webapp/channels/src/components/advanced_create_post/index.ts @@ -36,7 +36,7 @@ import type {ActionResult, GetStateFunc, DispatchFunc} from 'mattermost-redux/ty import {executeCommand} from 'actions/command'; import {runMessageWillBePostedHooks, runSlashCommandWillBePostedHooks} from 'actions/hooks'; -import {addReaction, createPost, setEditingPost, emitShortcutReactToLastPostFrom} from 'actions/post_actions'; +import {addReaction, createPost, setEditingPost, emitShortcutReactToLastPostFrom, submitReaction} from 'actions/post_actions'; import {actionOnGlobalItemsWithPrefix} from 'actions/storage'; import {scrollPostListToBottom} from 'actions/views/channel'; import {removeDraft, updateDraft} from 'actions/views/drafts'; @@ -153,6 +153,7 @@ type Actions = { addReaction: (postId: string, emojiName: string) => void; onSubmitPost: (post: Post, fileInfos: FileInfo[]) => void; removeReaction: (postId: string, emojiName: string) => void; + submitReaction: (postId: string, action: string, emojiName: string) => void; clearDraftUploads: () => void; runMessageWillBePostedHooks: (originalPost: Post) => ActionResult; runSlashCommandWillBePostedHooks: (originalMessage: string, originalArgs: CommandArgs) => ActionResult; @@ -202,6 +203,7 @@ function mapDispatchToProps(dispatch: Dispatch) { onSubmitPost, moveHistoryIndexBack, moveHistoryIndexForward, + submitReaction, addReaction, removeReaction, setDraft, diff --git a/webapp/channels/src/components/post_view/post_reaction/index.ts b/webapp/channels/src/components/post_view/post_reaction/index.ts index 437dc67b8e..30837b3866 100644 --- a/webapp/channels/src/components/post_view/post_reaction/index.ts +++ b/webapp/channels/src/components/post_view/post_reaction/index.ts @@ -7,7 +7,7 @@ import type {ActionCreatorsMapObject, Dispatch} from 'redux'; import type {Action} from 'mattermost-redux/types/actions'; -import {addReaction} from 'actions/post_actions'; +import {toggleReaction} from 'actions/post_actions'; import PostReaction from './post_reaction'; import type {Props} from './post_reaction'; @@ -15,7 +15,7 @@ import type {Props} from './post_reaction'; function mapDispatchToProps(dispatch: Dispatch) { return { actions: bindActionCreators, Props['actions']>({ - addReaction, + toggleReaction, }, dispatch), }; } diff --git a/webapp/channels/src/components/post_view/post_reaction/post_reaction.test.tsx b/webapp/channels/src/components/post_view/post_reaction/post_reaction.test.tsx index 60ffbaa9e4..540e3d4db3 100644 --- a/webapp/channels/src/components/post_view/post_reaction/post_reaction.test.tsx +++ b/webapp/channels/src/components/post_view/post_reaction/post_reaction.test.tsx @@ -18,7 +18,7 @@ describe('components/post_view/PostReaction', () => { showEmojiPicker: false, toggleEmojiPicker: jest.fn(), actions: { - addReaction: jest.fn(), + toggleReaction: jest.fn(), }, }; @@ -27,13 +27,13 @@ describe('components/post_view/PostReaction', () => { expect(wrapper).toMatchSnapshot(); }); - test('should call addReaction and toggleEmojiPicker on handleAddEmoji', () => { + test('should call toggleReaction and toggleEmojiPicker on handleToggleEmoji', () => { const wrapper = shallow(); const instance = wrapper.instance() as PostReaction; - instance.handleAddEmoji({name: 'smile'} as Emoji); - expect(baseProps.actions.addReaction).toHaveBeenCalledTimes(1); - expect(baseProps.actions.addReaction).toHaveBeenCalledWith('post_id_1', 'smile'); + instance.handleToggleEmoji({name: 'smile'} as Emoji); + expect(baseProps.actions.toggleReaction).toHaveBeenCalledTimes(1); + expect(baseProps.actions.toggleReaction).toHaveBeenCalledWith('post_id_1', 'smile'); expect(baseProps.toggleEmojiPicker).toHaveBeenCalledTimes(1); }); }); diff --git a/webapp/channels/src/components/post_view/post_reaction/post_reaction.tsx b/webapp/channels/src/components/post_view/post_reaction/post_reaction.tsx index d5cb0dd8eb..0bb3389e78 100644 --- a/webapp/channels/src/components/post_view/post_reaction/post_reaction.tsx +++ b/webapp/channels/src/components/post_view/post_reaction/post_reaction.tsx @@ -30,7 +30,7 @@ export type Props = { showEmojiPicker: boolean; toggleEmojiPicker: (e?: React.MouseEvent) => void; actions: { - addReaction: (postId: string, emojiName: string) => (dispatch: Dispatch) => {data: boolean}; + toggleReaction: (postId: string, emojiName: string) => (dispatch: Dispatch) => {data: boolean}; }; } @@ -45,10 +45,10 @@ export default class PostReaction extends React.PureComponent { showEmojiPicker: false, }; - handleAddEmoji = (emoji: Emoji): void => { + handleToggleEmoji = (emoji: Emoji): void => { this.setState({showEmojiPicker: false}); const emojiName = 'short_name' in emoji ? emoji.short_name : emoji.name; - this.props.actions.addReaction(this.props.postId, emojiName); + this.props.actions.toggleReaction(this.props.postId, emojiName); this.props.toggleEmojiPicker(); }; @@ -79,7 +79,7 @@ export default class PostReaction extends React.PureComponent { show={showEmojiPicker} target={this.props.getDotMenuRef} onHide={this.props.toggleEmojiPicker} - onEmojiClick={this.handleAddEmoji} + onEmojiClick={this.handleToggleEmoji} topOffset={TOP_OFFSET} spaceRequiredAbove={spaceRequiredAbove} spaceRequiredBelow={spaceRequiredBelow} diff --git a/webapp/channels/src/components/post_view/post_recent_reactions/index.ts b/webapp/channels/src/components/post_view/post_recent_reactions/index.ts index 518cd3698e..e6de14d541 100644 --- a/webapp/channels/src/components/post_view/post_recent_reactions/index.ts +++ b/webapp/channels/src/components/post_view/post_recent_reactions/index.ts @@ -9,7 +9,7 @@ import type {Emoji} from '@mattermost/types/emojis'; import type {GenericAction} from 'mattermost-redux/types/actions'; -import {addReaction} from 'actions/post_actions'; +import {toggleReaction} from 'actions/post_actions'; import {getEmojiMap} from 'selectors/emojis'; import {getCurrentLocale} from 'selectors/i18n'; @@ -20,7 +20,7 @@ import PostReaction from './post_recent_reactions'; function mapDispatchToProps(dispatch: Dispatch) { return { actions: bindActionCreators({ - addReaction, + toggleReaction, }, dispatch), }; } diff --git a/webapp/channels/src/components/post_view/post_recent_reactions/post_recent_reactions.tsx b/webapp/channels/src/components/post_view/post_recent_reactions/post_recent_reactions.tsx index d9d81f1012..ead83dd146 100644 --- a/webapp/channels/src/components/post_view/post_recent_reactions/post_recent_reactions.tsx +++ b/webapp/channels/src/components/post_view/post_recent_reactions/post_recent_reactions.tsx @@ -27,7 +27,7 @@ type Props = { size: number; defaultEmojis: Emoji[]; actions: { - addReaction: (postId: string, emojiName: string) => void; + toggleReaction: (postId: string, emojiName: string) => void; }; } @@ -41,9 +41,9 @@ export default class PostRecentReactions extends React.PureComponent { + handleToggleEmoji = (emoji: Emoji): void => { const emojiName = 'short_name' in emoji ? emoji.short_name : emoji.name; - this.props.actions.addReaction(this.props.postId, emojiName); + this.props.actions.toggleReaction(this.props.postId, emojiName); }; complementEmojis = (emojis: Emoji[]): (Emoji[]) => { @@ -108,7 +108,7 @@ export default class PostRecentReactions extends React.PureComponent diff --git a/webapp/channels/src/components/post_view/reaction_list/index.ts b/webapp/channels/src/components/post_view/reaction_list/index.ts index 33e30d241d..2e858c9043 100644 --- a/webapp/channels/src/components/post_view/reaction_list/index.ts +++ b/webapp/channels/src/components/post_view/reaction_list/index.ts @@ -11,7 +11,7 @@ import {getChannel} from 'mattermost-redux/selectors/entities/channels'; import {canAddReactions} from 'mattermost-redux/selectors/entities/reactions'; import type {GenericAction} from 'mattermost-redux/types/actions'; -import {addReaction} from 'actions/post_actions'; +import {toggleReaction} from 'actions/post_actions'; import {makeGetUniqueReactionsToPost} from 'utils/post_utils'; @@ -43,7 +43,7 @@ function makeMapStateToProps() { function mapDispatchToProps(dispatch: Dispatch) { return { actions: bindActionCreators({ - addReaction, + toggleReaction, }, dispatch), }; } diff --git a/webapp/channels/src/components/post_view/reaction_list/reaction_list.tsx b/webapp/channels/src/components/post_view/reaction_list/reaction_list.tsx index ca4daf145f..b52e44ceb5 100644 --- a/webapp/channels/src/components/post_view/reaction_list/reaction_list.tsx +++ b/webapp/channels/src/components/post_view/reaction_list/reaction_list.tsx @@ -51,7 +51,7 @@ type Props = { /** * Function to add a reaction to the post */ - addReaction: (postId: string, emojiName: string) => void; + toggleReaction: (postId: string, emojiName: string) => void; }; }; @@ -91,7 +91,7 @@ export default class ReactionList extends React.PureComponent { handleEmojiClick = (emoji: Emoji): void => { this.setState({showEmojiPicker: false}); const emojiName = isSystemEmoji(emoji) ? emoji.short_names[0] : emoji.name; - this.props.actions.addReaction(this.props.post.id, emojiName); + this.props.actions.toggleReaction(this.props.post.id, emojiName); }; hideEmojiPicker = (): void => { diff --git a/webapp/channels/src/components/post_view/reaction_list/reactions_list.test.tsx b/webapp/channels/src/components/post_view/reaction_list/reactions_list.test.tsx index 942295de91..d9c02b1a57 100644 --- a/webapp/channels/src/components/post_view/reaction_list/reactions_list.test.tsx +++ b/webapp/channels/src/components/post_view/reaction_list/reactions_list.test.tsx @@ -27,7 +27,7 @@ describe('components/ReactionList', () => { const teamId = 'teamId'; const actions = { - addReaction: jest.fn(), + toggleReaction: jest.fn(), }; const baseProps = { diff --git a/webapp/channels/src/utils/post_utils.test.tsx b/webapp/channels/src/utils/post_utils.test.tsx index 4c4b89d65e..b2e893e696 100644 --- a/webapp/channels/src/utils/post_utils.test.tsx +++ b/webapp/channels/src/utils/post_utils.test.tsx @@ -1240,10 +1240,10 @@ describe('PostUtils.isWithinCodeBlock', () => { it('should handle whitespace within and around code blocks', () => { const [caretPosition, message] = getCaretAndMsg(` - |${TRIPLE_BACKTICKS} - | Test text asd 1 - | ${CARET_MARKER} - |${TRIPLE_BACKTICKS} + |${TRIPLE_BACKTICKS} + | Test text asd 1 + | ${CARET_MARKER} + |${TRIPLE_BACKTICKS} `); expect(PostUtils.isWithinCodeBlock(message, caretPosition)).toBe(true); }); @@ -1319,3 +1319,37 @@ describe('PostUtils.getUserOrGroupFromMentionName', () => { expect(result).toEqual(expected); }); }); + +describe('makeGetIsReactionAlreadyAddedToPost', () => { + const currentUserId = 'current_user_id'; + + const baseState = { + entities: { + users: { + currentUserId, + }, + posts: { + reactions: { + post_id_1: { + 'current_user_id-smile': { + emoji_name: 'smile', + user_id: currentUserId, + post_id: 'post_id_1', + }, + }, + + }, + }, + general: { + config: {}, + }, + emojis: {}, + }} as unknown as GlobalState; + + test('should return true if the post has an emoji that the user has reacted to.', () => { + const getIsReactionAlreadyAddedToPost = PostUtils.makeGetIsReactionAlreadyAddedToPost(); + + expect(getIsReactionAlreadyAddedToPost(baseState, 'post_id_1', 'sad')).toBeFalsy(); + expect(getIsReactionAlreadyAddedToPost(baseState, 'post_id_1', 'smile')).toBeTruthy(); + }); +}); diff --git a/webapp/channels/src/utils/post_utils.ts b/webapp/channels/src/utils/post_utils.ts index 7fb7d7f39e..9a60d3cbab 100644 --- a/webapp/channels/src/utils/post_utils.ts +++ b/webapp/channels/src/utils/post_utils.ts @@ -710,6 +710,24 @@ export function makeGetUniqueReactionsToPost(): (state: GlobalState, postId: Pos ); } +export function makeGetIsReactionAlreadyAddedToPost(): (state: GlobalState, postId: Post['id'], emojiName: string) => boolean { + const getUniqueReactionsToPost = makeGetUniqueReactionsToPost(); + + return createSelector( + 'makeGetIsReactionAlreadyAddedToPost', + (state: GlobalState, postId: string) => getUniqueReactionsToPost(state, postId), + getCurrentUserId, + (state: GlobalState, postId: string, emojiName: string) => emojiName, + (reactions, currentUserId, emojiName) => { + const reactionsForPost = reactions || {}; + + const isReactionAlreadyAddedToPost = Object.values(reactionsForPost).some((reaction) => reaction.user_id === currentUserId && reaction.emoji_name === emojiName); + + return isReactionAlreadyAddedToPost; + }, + ); +} + export function getMentionDetails(usersByUsername: Record, mentionName: string): UserProfile | Group | undefined { let mentionNameToLowerCase = mentionName.toLowerCase();