From 5a540ea23ef0df2c32527cf4d59252e4d209626e Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Thu, 22 Jun 2023 15:41:43 -0400 Subject: [PATCH] MM-53164 Batch adding multiple custom emojis when making a post (#23822) --- webapp/channels/src/actions/emoji_actions.js | 60 ++++++++++--------- .../src/actions/emoji_actions.test.js | 45 ++++++++++++++ .../channels/src/actions/post_actions.test.ts | 16 +++-- webapp/channels/src/actions/post_actions.ts | 8 +-- 4 files changed, 88 insertions(+), 41 deletions(-) diff --git a/webapp/channels/src/actions/emoji_actions.js b/webapp/channels/src/actions/emoji_actions.js index 78bbad8995..f704572b82 100644 --- a/webapp/channels/src/actions/emoji_actions.js +++ b/webapp/channels/src/actions/emoji_actions.js @@ -60,43 +60,49 @@ export function setUserSkinTone(skin) { }; } +export function addRecentEmoji(alias) { + return addRecentEmojis([alias]); +} + export const MAXIMUM_RECENT_EMOJI = 27; -export function addRecentEmoji(alias) { +export function addRecentEmojis(aliases) { return (dispatch, getState) => { const state = getState(); const currentUserId = getCurrentUserId(state); const recentEmojis = getRecentEmojisData(state); const emojiMap = getEmojiMap(state); - let name; - const emoji = emojiMap.get(alias); - if (!emoji) { - return {data: false}; - } else if (emoji.short_name) { - name = emoji.short_name; - } else { - name = emoji.name; - } + let updatedRecentEmojis = [...recentEmojis]; + for (const alias of aliases) { + let name; + const emoji = emojiMap.get(alias); + if (!emoji) { + continue; + } else if (emoji.short_name) { + name = emoji.short_name; + } else { + name = emoji.name; + } - let updatedRecentEmojis; - const currentEmojiIndexInRecentList = recentEmojis.findIndex((recentEmoji) => recentEmoji.name === name); - if (currentEmojiIndexInRecentList > -1) { - const currentEmojiInRecentList = recentEmojis[currentEmojiIndexInRecentList]; + const currentEmojiIndexInRecentList = updatedRecentEmojis.findIndex((recentEmoji) => recentEmoji.name === name); + if (currentEmojiIndexInRecentList > -1) { + const currentEmojiInRecentList = updatedRecentEmojis[currentEmojiIndexInRecentList]; - // If the emoji is already in the recent list, remove it and add it to the front with updated usage count - const updatedCurrentEmojiData = { - name, - usageCount: currentEmojiInRecentList.usageCount + 1, - }; - recentEmojis.splice(currentEmojiIndexInRecentList, 1); - updatedRecentEmojis = [...recentEmojis, updatedCurrentEmojiData].slice(-MAXIMUM_RECENT_EMOJI); - } else { - const currentEmojiData = { - name, - usageCount: 1, - }; - updatedRecentEmojis = [...recentEmojis, currentEmojiData].slice(-MAXIMUM_RECENT_EMOJI); + // If the emoji is already in the recent list, remove it and add it to the front with updated usage count + const updatedCurrentEmojiData = { + name, + usageCount: currentEmojiInRecentList.usageCount + 1, + }; + updatedRecentEmojis.splice(currentEmojiIndexInRecentList, 1); + updatedRecentEmojis = [...updatedRecentEmojis, updatedCurrentEmojiData].slice(-MAXIMUM_RECENT_EMOJI); + } else { + const currentEmojiData = { + name, + usageCount: 1, + }; + updatedRecentEmojis = [...updatedRecentEmojis, currentEmojiData].slice(-MAXIMUM_RECENT_EMOJI); + } } // sort emojis by count in the ascending order diff --git a/webapp/channels/src/actions/emoji_actions.test.js b/webapp/channels/src/actions/emoji_actions.test.js index 478d0593db..741ed3cf45 100644 --- a/webapp/channels/src/actions/emoji_actions.test.js +++ b/webapp/channels/src/actions/emoji_actions.test.js @@ -236,4 +236,49 @@ describe('Actions.Emojis', () => { await store.dispatch(EmojiActions.addRecentEmoji('accept')); expect(store.getActions()).toEqual(expectedActions); }); + + test('Should add multiple emojis to recent emojis at once', async () => { + const recentEmojisList = [ + {name: 'trumpet', usageCount: 1}, + {name: 'balloon', usageCount: 3}, + {name: 'taco', usageCount: 4}, + ]; + getRecentEmojisData.mockImplementation(() => { + return recentEmojisList; + }); + + getEmojiMap.mockImplementation(() => { + return new Map([ + ['accept', {short_name: 'accept'}], + ['balloon', {short_name: 'balloon'}], + ['grinning', {short_name: 'grinning'}], + ['taco', {short_name: 'taco'}], + ['trumpet', {short_name: 'trumpet'}], + ]); + }); + + const expectedActions = [{ + type: 'RECEIVED_PREFERENCES', + args: [ + 'current_user_id', + [ + { + category: 'recent_emojis', + name: 'current_user_id', + user_id: 'current_user_id', + value: JSON.stringify([ + {name: 'trumpet', usageCount: 1}, + {name: 'grinning', usageCount: 1}, + {name: 'accept', usageCount: 2}, + {name: 'taco', usageCount: 4}, + {name: 'balloon', usageCount: 5}, + ]), + }, + ], + ], + }]; + + await store.dispatch(EmojiActions.addRecentEmojis(['balloon', 'grinning', 'accept', 'balloon', 'accept'])); + expect(store.getActions()).toEqual(expectedActions); + }); }); diff --git a/webapp/channels/src/actions/post_actions.test.ts b/webapp/channels/src/actions/post_actions.test.ts index 2fbd104dbc..7219c4f818 100644 --- a/webapp/channels/src/actions/post_actions.test.ts +++ b/webapp/channels/src/actions/post_actions.test.ts @@ -27,6 +27,7 @@ jest.mock('mattermost-redux/actions/posts', () => ({ jest.mock('actions/emoji_actions', () => ({ addRecentEmoji: (...args: any[]) => ({type: 'MOCK_ADD_RECENT_EMOJI', args}), + addRecentEmojis: (...args: any[]) => ({type: 'MOCK_ADD_RECENT_EMOJIS', args}), })); jest.mock('actions/notification_actions', () => ({ @@ -376,8 +377,8 @@ describe('Actions.Posts', () => { const files: FileInfo[] = []; const immediateExpectedState = [{ - args: ['+1'], - type: 'MOCK_ADD_RECENT_EMOJI', + args: [['+1']], + type: 'MOCK_ADD_RECENT_EMOJIS', }, { args: [newPost, files], type: 'MOCK_CREATE_POST', @@ -396,8 +397,8 @@ describe('Actions.Posts', () => { const files: FileInfo[] = []; const immediateExpectedState = [{ - args: ['cake'], - type: 'MOCK_ADD_RECENT_EMOJI', + args: [['cake']], + type: 'MOCK_ADD_RECENT_EMOJIS', }, { args: [newPost, files], type: 'MOCK_CREATE_POST', @@ -416,11 +417,8 @@ describe('Actions.Posts', () => { const files: FileInfo[] = []; const immediateExpectedState = [{ - args: ['cake'], - type: 'MOCK_ADD_RECENT_EMOJI', - }, { - args: ['+1'], - type: 'MOCK_ADD_RECENT_EMOJI', + args: [['cake', '+1']], + type: 'MOCK_ADD_RECENT_EMOJIS', }, { args: [newPost, files], type: 'MOCK_CREATE_POST', diff --git a/webapp/channels/src/actions/post_actions.ts b/webapp/channels/src/actions/post_actions.ts index 476fc5394b..ce234d7b0c 100644 --- a/webapp/channels/src/actions/post_actions.ts +++ b/webapp/channels/src/actions/post_actions.ts @@ -17,7 +17,7 @@ import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {canEditPost, comparePosts} from 'mattermost-redux/utils/post_utils'; import {DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions'; -import {addRecentEmoji} from 'actions/emoji_actions'; +import {addRecentEmoji, addRecentEmojis} from 'actions/emoji_actions'; import * as StorageActions from 'actions/storage'; import {loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions'; import * as RhsActions from 'actions/views/rhs'; @@ -102,10 +102,8 @@ export function createPost(post: Post, files: FileInfo[]) { // parse message and emit emoji event const emojis = matchEmoticons(post.message); if (emojis) { - for (const emoji of emojis) { - const trimmed = emoji.substring(1, emoji.length - 1); - dispatch(addRecentEmoji(trimmed)); - } + const trimmedEmojis = emojis.map((emoji) => emoji.substring(1, emoji.length - 1)); + dispatch(addRecentEmojis(trimmedEmojis)); } let result;