From 9448c83b3af8e983cd0f90ef898394ba5fda5280 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 4 May 2023 15:49:03 +0800 Subject: [PATCH 01/10] remove frequent emoji list onyx connection --- .../EmojiPicker/EmojiPickerMenu/index.js | 16 +++------------- .../EmojiPicker/EmojiPickerMenu/index.native.js | 14 ++------------ src/pages/home/report/ReportActionCompose.js | 12 +----------- 3 files changed, 6 insertions(+), 36 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 9b1e5e5b2ee8..b1a80d0303dc 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -32,12 +32,6 @@ const propTypes = { /** Stores user's preferred skin tone */ preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - /** User's frequently used emojis */ - frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.shape({ - code: PropTypes.string.isRequired, - keywords: PropTypes.arrayOf(PropTypes.string), - })), - /** Props related to the dimensions of the window */ ...windowDimensionsPropTypes, @@ -47,7 +41,6 @@ const propTypes = { const defaultProps = { forwardedRef: () => {}, preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, - frequentlyUsedEmojis: [], }; class EmojiPickerMenu extends Component { @@ -64,8 +57,8 @@ class EmojiPickerMenu extends Component { // since Windows doesn't support them const flagHeaderIndex = _.findIndex(emojis, emoji => emoji.header && emoji.code === 'flags'); this.emojis = getOperatingSystem() === CONST.OS.WINDOWS - ? EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis.slice(0, flagHeaderIndex), this.props.frequentlyUsedEmojis) - : EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis, this.props.frequentlyUsedEmojis); + ? EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis.slice(0, flagHeaderIndex)) + : EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis); // Get the header emojis along with the code, index and icon. // index is the actual header index starting at the first emoji and counting each one @@ -234,7 +227,7 @@ class EmojiPickerMenu extends Component { * @param {Object} emojiObject */ addToFrequentAndSelectEmoji(emoji, emojiObject) { - EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + EmojiUtils.addToFrequentlyUsedEmojis(emojiObject); this.props.onEmojiSelected(emoji, emojiObject); } @@ -569,9 +562,6 @@ export default compose( preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, }, - frequentlyUsedEmojis: { - key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, - }, }), )(React.forwardRef((props, ref) => ( // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js index 702be109e5e1..078b053051d8 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js @@ -25,12 +25,6 @@ const propTypes = { /** Stores user's preferred skin tone */ preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - /** User's frequently used emojis */ - frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.shape({ - code: PropTypes.string.isRequired, - keywords: PropTypes.arrayOf(PropTypes.string), - })), - /** Props related to the dimensions of the window */ ...windowDimensionsPropTypes, @@ -40,7 +34,6 @@ const propTypes = { const defaultProps = { preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, - frequentlyUsedEmojis: [], }; class EmojiPickerMenu extends Component { @@ -50,7 +43,7 @@ class EmojiPickerMenu extends Component { // Ref for emoji FlatList this.emojiList = undefined; - this.emojis = EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis, this.props.frequentlyUsedEmojis); + this.emojis = EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis); // Get the header emojis along with the code, index and icon. // index is the actual header index starting at the first emoji and counting each one @@ -77,7 +70,7 @@ class EmojiPickerMenu extends Component { * @param {Object} emojiObject */ addToFrequentAndSelectEmoji(emoji, emojiObject) { - EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + EmojiUtils.addToFrequentlyUsedEmojis(emojiObject); this.props.onEmojiSelected(emoji, emojiObject); } @@ -190,9 +183,6 @@ export default compose( preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, }, - frequentlyUsedEmojis: { - key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, - }, }), )(React.forwardRef((props, ref) => ( // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 5e589b499898..b1871d5093a4 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -112,12 +112,6 @@ const propTypes = { /** Stores user's preferred skin tone */ preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - /** User's frequently used emojis */ - frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.shape({ - code: PropTypes.string.isRequired, - keywords: PropTypes.arrayOf(PropTypes.string), - })), - /** The type of action that's pending */ pendingAction: PropTypes.oneOf(['add', 'update', 'delete']), @@ -137,7 +131,6 @@ const defaultProps = { blockedFromConcierge: {}, personalDetails: {}, preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, - frequentlyUsedEmojis: [], isComposerFullSize: false, pendingAction: null, ...withCurrentUserPersonalDetailsDefaultProps, @@ -519,7 +512,7 @@ class ReportActionCompose extends React.Component { }, suggestedEmojis: [], })); - EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject); + EmojiUtils.addToFrequentlyUsedEmojis(emojiObject); } isEmptyChat() { @@ -1045,9 +1038,6 @@ export default compose( blockedFromConcierge: { key: ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE, }, - frequentlyUsedEmojis: { - key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, - }, preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, }, From 4f94c44bf1ff29b298d27afbd1df2347da73f0ea Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 4 May 2023 15:50:40 +0800 Subject: [PATCH 02/10] connect with frequent emojis list onyx and update the list when emoji is added by emoji code and colon --- src/libs/EmojiUtils.js | 51 +++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index 1236e3df9ff6..f9abdf8f87b3 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -2,11 +2,19 @@ import _ from 'underscore'; import lodashOrderBy from 'lodash/orderBy'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import * as User from './actions/User'; import emojisTrie from './EmojiTrie'; import FrequentlyUsed from '../../assets/images/history.svg'; +let frequentlyUsedEmojis = []; +Onyx.connect({ + key: ONYXKEYS.FREQUENTLY_USED_EMOJIS, + callback: val => frequentlyUsedEmojis = val, +}); + /** * Get the unicode code of an emoji in base 16. * @param {String} input @@ -139,10 +147,9 @@ function addSpacesToEmojiCategories(emojis) { /** * Get a merged array with frequently used emojis * @param {Object[]} emojis - * @param {Object[]} frequentlyUsedEmojis * @returns {Object[]} */ -function mergeEmojisWithFrequentlyUsedEmojis(emojis, frequentlyUsedEmojis = []) { +function mergeEmojisWithFrequentlyUsedEmojis(emojis) { if (frequentlyUsedEmojis.length === 0) { return addSpacesToEmojiCategories(emojis); } @@ -159,29 +166,28 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis, frequentlyUsedEmojis = []) /** * Update the frequently used emojis list by usage and sync with API - * @param {Object[]} frequentlyUsedEmojis - * @param {Object} newEmoji + * @param {Object|Object[]} newEmoji */ -function addToFrequentlyUsedEmojis(frequentlyUsedEmojis, newEmoji) { - let frequentEmojiList = frequentlyUsedEmojis; - let currentEmojiCount = 1; +function addToFrequentlyUsedEmojis(newEmoji) { + let frequentEmojiList = [...frequentlyUsedEmojis]; + const currentTimestamp = moment().unix(); - const emojiIndex = _.findIndex(frequentEmojiList, e => e.code === newEmoji.code); - if (emojiIndex >= 0) { - currentEmojiCount = frequentEmojiList[emojiIndex].count + 1; - frequentEmojiList.splice(emojiIndex, 1); - } - const updatedEmoji = {...newEmoji, ...{count: currentEmojiCount, lastUpdatedAt: currentTimestamp}}; - const maxFrequentEmojiCount = (CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW) - 1; + _.each(newEmoji, (emoji) => { + let currentEmojiCount = 1; + const emojiIndex = _.findIndex(frequentEmojiList, e => e.code === emoji.code); + if (emojiIndex >= 0) { + currentEmojiCount = frequentEmojiList[emojiIndex].count + 1; + frequentEmojiList.splice(emojiIndex, 1); + } + const updatedEmoji = {...emoji, ...{count: currentEmojiCount, lastUpdatedAt: currentTimestamp}}; + frequentEmojiList.push(updatedEmoji); + }); - // We want to make sure the current emoji is added to the list - // Hence, we take one less than the current high frequent used emojis and if same then sorted by lastUpdatedAt - frequentEmojiList = lodashOrderBy(frequentEmojiList, ['count', 'lastUpdatedAt'], ['desc', 'desc']); - frequentEmojiList = frequentEmojiList.slice(0, maxFrequentEmojiCount); - frequentEmojiList.push(updatedEmoji); + const maxFrequentEmojiCount = (CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW); - // Second sorting is required so that new emoji is properly placed at sort-ordered location + // Sort the list and take the first maxFrequentEmojiCount items frequentEmojiList = lodashOrderBy(frequentEmojiList, ['count', 'lastUpdatedAt'], ['desc', 'desc']); + frequentEmojiList = frequentEmojiList.slice(0, maxFrequentEmojiCount); User.updateFrequentlyUsedEmojis(frequentEmojiList); } @@ -215,10 +221,12 @@ function replaceEmojis(text, isSmallScreenWidth = false, preferredSkinTone = CON if (!emojiData || emojiData.length === 0) { return text; } + const emojis = []; for (let i = 0; i < emojiData.length; i++) { const checkEmoji = emojisTrie.search(emojiData[i].slice(1, -1)); if (checkEmoji && checkEmoji.metaData.code) { let emojiReplacement = getEmojiCodeWithSkinColor(checkEmoji.metaData, preferredSkinTone); + emojis.push({code: emojiReplacement}); // If this is the last emoji in the message and it's the end of the message so far, // add a space after it so the user can keep typing easily. @@ -228,6 +236,9 @@ function replaceEmojis(text, isSmallScreenWidth = false, preferredSkinTone = CON newText = newText.replace(emojiData[i], emojiReplacement); } } + + // Add all replaced emojis to the frequently used emojis list + addToFrequentlyUsedEmojis(emojis); return newText; } From b0eeb72111b6ac089ea70db89b1759a479cc8b2e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 5 May 2023 13:53:32 +0800 Subject: [PATCH 03/10] improve the logic --- src/libs/EmojiUtils.js | 59 ++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index f9abdf8f87b3..d9f3e748f270 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -1,4 +1,6 @@ import _ from 'underscore'; +import lodashSumBy from 'lodash/sumBy'; +import lodashMaxBy from 'lodash/maxBy'; import lodashOrderBy from 'lodash/orderBy'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; @@ -169,26 +171,35 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis) { * @param {Object|Object[]} newEmoji */ function addToFrequentlyUsedEmojis(newEmoji) { - let frequentEmojiList = [...frequentlyUsedEmojis]; - const currentTimestamp = moment().unix(); - _.each(newEmoji, (emoji) => { - let currentEmojiCount = 1; - const emojiIndex = _.findIndex(frequentEmojiList, e => e.code === emoji.code); - if (emojiIndex >= 0) { - currentEmojiCount = frequentEmojiList[emojiIndex].count + 1; - frequentEmojiList.splice(emojiIndex, 1); - } - const updatedEmoji = {...emoji, ...{count: currentEmojiCount, lastUpdatedAt: currentTimestamp}}; - frequentEmojiList.push(updatedEmoji); - }); - const maxFrequentEmojiCount = (CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW); + // Get unique emojis array with counts for every emoji, these emojis are usually extracted from copy/pasted comments + const uniqueEmojisWithCounts = _.chain([].concat(newEmoji)) + .groupBy('name') + .map(values => ({ + ...values[0], + count: values.length, + lastUpdatedAt: currentTimestamp, + })) + .value(); + + // Concat uniqueEmojisWithCounts with the frequentlyUsedEmojis where we sum the count and update the lastUpdatedAt + const mergedEmojisWithFrequent = _.chain(uniqueEmojisWithCounts) + .concat(frequentlyUsedEmojis) + .groupBy('name') + .map(groupedEmojiList => ({ + ...groupedEmojiList[0], + count: lodashSumBy(groupedEmojiList, 'count'), + lastUpdatedAt: lodashMaxBy(groupedEmojiList, 'lastUpdatedAt').lastUpdatedAt, + })) + .value(); + // Sort the list and take the first maxFrequentEmojiCount items - frequentEmojiList = lodashOrderBy(frequentEmojiList, ['count', 'lastUpdatedAt'], ['desc', 'desc']); - frequentEmojiList = frequentEmojiList.slice(0, maxFrequentEmojiCount); - User.updateFrequentlyUsedEmojis(frequentEmojiList); + const frequentEmojiListOrdered = lodashOrderBy(mergedEmojisWithFrequent, ['count', 'lastUpdatedAt'], ['desc', 'desc']) + .slice(0, maxFrequentEmojiCount); + + User.updateFrequentlyUsedEmojis(frequentEmojiListOrdered); } /** @@ -210,6 +221,9 @@ const getEmojiCodeWithSkinColor = (item, preferredSkinToneIndex) => { /** * Replace any emoji name in a text with the emoji icon. * If we're on mobile, we also add a space after the emoji granted there's no text after it. + * + * All replaced emojis will be added to the frequently used emojis list. + * * @param {String} text * @param {Boolean} isSmallScreenWidth * @param {Number} preferredSkinTone @@ -223,10 +237,15 @@ function replaceEmojis(text, isSmallScreenWidth = false, preferredSkinTone = CON } const emojis = []; for (let i = 0; i < emojiData.length; i++) { - const checkEmoji = emojisTrie.search(emojiData[i].slice(1, -1)); + const name = emojiData[i].slice(1, -1); + const checkEmoji = emojisTrie.search(name); if (checkEmoji && checkEmoji.metaData.code) { let emojiReplacement = getEmojiCodeWithSkinColor(checkEmoji.metaData, preferredSkinTone); - emojis.push({code: emojiReplacement}); + emojis.push({ + name, + code: checkEmoji.metaData.code, + types: checkEmoji.metaData.types, + }); // If this is the last emoji in the message and it's the end of the message so far, // add a space after it so the user can keep typing easily. @@ -238,7 +257,9 @@ function replaceEmojis(text, isSmallScreenWidth = false, preferredSkinTone = CON } // Add all replaced emojis to the frequently used emojis list - addToFrequentlyUsedEmojis(emojis); + if (!_.isEmpty(emojis)) { + addToFrequentlyUsedEmojis(emojis); + } return newText; } From f1184b86faeae13276b11fa6f7a0f19b1ea185b4 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 6 May 2023 18:03:36 +0800 Subject: [PATCH 04/10] fix can't add new emoji to the list --- src/libs/EmojiUtils.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index d9f3e748f270..0784bcfccd7f 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -2,6 +2,8 @@ import _ from 'underscore'; import lodashSumBy from 'lodash/sumBy'; import lodashMaxBy from 'lodash/maxBy'; import lodashOrderBy from 'lodash/orderBy'; +import lodashIntersectionBy from 'lodash/intersectionBy'; +import lodashDifferenceBy from 'lodash/differenceBy'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; @@ -172,10 +174,9 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis) { */ function addToFrequentlyUsedEmojis(newEmoji) { const currentTimestamp = moment().unix(); - const maxFrequentEmojiCount = (CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW); // Get unique emojis array with counts for every emoji, these emojis are usually extracted from copy/pasted comments - const uniqueEmojisWithCounts = _.chain([].concat(newEmoji)) + const uniqueEmojisWithCount = _.chain([].concat(newEmoji)) .groupBy('name') .map(values => ({ ...values[0], @@ -184,8 +185,8 @@ function addToFrequentlyUsedEmojis(newEmoji) { })) .value(); - // Concat uniqueEmojisWithCounts with the frequentlyUsedEmojis where we sum the count and update the lastUpdatedAt - const mergedEmojisWithFrequent = _.chain(uniqueEmojisWithCounts) + // Concat uniqueEmojisWithCount with the frequentlyUsedEmojis just to sum the count and update the lastUpdatedAt + const mergedEmojisWithFrequent = _.chain(uniqueEmojisWithCount) .concat(frequentlyUsedEmojis) .groupBy('name') .map(groupedEmojiList => ({ @@ -195,11 +196,20 @@ function addToFrequentlyUsedEmojis(newEmoji) { })) .value(); - // Sort the list and take the first maxFrequentEmojiCount items - const frequentEmojiListOrdered = lodashOrderBy(mergedEmojisWithFrequent, ['count', 'lastUpdatedAt'], ['desc', 'desc']) - .slice(0, maxFrequentEmojiCount); + const maxFrequentEmojiCount = (CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW) - uniqueEmojisWithCount.length; - User.updateFrequentlyUsedEmojis(frequentEmojiListOrdered); + // After we get the new count and lastUpdatedAt, take out the updated unique emojis from mergedEmojisWithFrequent + const emojisWithNewCount = lodashIntersectionBy(mergedEmojisWithFrequent, uniqueEmojisWithCount, 'name'); + const frequentEmojisList = lodashDifferenceBy(mergedEmojisWithFrequent, emojisWithNewCount, 'name'); + + // Take the first maxFrequentEmojiCount items, push back the emojisWithNewCount, and sort the list by count and lastUpdatedAt + const frequentEmojisListOrdered = lodashOrderBy( + [...frequentEmojisList.slice(0, maxFrequentEmojiCount), ...emojisWithNewCount], + ['count', 'lastUpdatedAt'], + ['desc', 'desc'], + ); + + User.updateFrequentlyUsedEmojis(frequentEmojisListOrdered); } /** From a9e689ee82b32d8383175968df891ea8b48018cc Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 6 May 2023 18:03:47 +0800 Subject: [PATCH 05/10] add test --- tests/unit/EmojiTest.js | 221 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/tests/unit/EmojiTest.js b/tests/unit/EmojiTest.js index 224e3adafc05..d9f8edac401b 100644 --- a/tests/unit/EmojiTest.js +++ b/tests/unit/EmojiTest.js @@ -1,6 +1,12 @@ import _ from 'underscore'; +import moment from 'moment'; +import Onyx from 'react-native-onyx'; import Emoji from '../../assets/emojis'; import * as EmojiUtils from '../../src/libs/EmojiUtils'; +import ONYXKEYS from '../../src/ONYXKEYS'; +import * as User from '../../src/libs/actions/User'; +import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import * as TestHelper from '../utils/TestHelper'; describe('EmojiTest', () => { it('matches all the emojis in the list', () => { @@ -147,4 +153,219 @@ describe('EmojiTest', () => { ], }]); }); + + describe('update frequently used emojis', () => { + let spy; + + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + global.fetch = TestHelper.getGlobalFetchMock(); + spy = jest.spyOn(User, 'updateFrequentlyUsedEmojis'); + }); + + beforeEach(() => { + spy.mockClear(); + Onyx.clear(); + return waitForPromisesToResolve(); + }); + + it('should put a less frequent and recent used emoji behind', () => { + const frequentlyEmojisList = [ + { + code: '👋', name: 'wave', count: 2, lastUpdatedAt: 4, + }, + { + code: '💤', name: 'zzz', count: 2, lastUpdatedAt: 3, + }, + { + code: '💯', name: '100', count: 2, lastUpdatedAt: 2, + }, + { + code: '👿', name: 'imp', count: 2, lastUpdatedAt: 1, + }, + ]; + Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); + + return waitForPromisesToResolve() + .then(() => { + const currentTime = moment().unix(); + const smileEmoji = {code: '😄', name: 'smile'}; + const newEmoji = [smileEmoji]; + EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + + const expectedSmileEmoj = {...smileEmoji, count: 1, lastUpdatedAt: currentTime}; + const expectedFrequentlyEmojisList = [...frequentlyEmojisList, expectedSmileEmoj]; + expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); + }); + }); + + it('should put more frequent and recent used emoji to the front', () => { + const smileEmoji = {code: '😄', name: 'smile'}; + const frequentlyEmojisList = [ + { + code: '😠', name: 'angry', count: 3, lastUpdatedAt: 5, + }, + { + code: '👋', name: 'wave', count: 2, lastUpdatedAt: 4, + }, + { + code: '💤', name: 'zzz', count: 2, lastUpdatedAt: 3, + }, + { + code: '💯', name: '100', count: 1, lastUpdatedAt: 2, + }, + {...smileEmoji, count: 1, lastUpdatedAt: 1}, + ]; + Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); + + return waitForPromisesToResolve() + .then(() => { + const currentTime = moment().unix(); + const newEmoji = [smileEmoji]; + EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + + const expectedFrequentlyEmojisList = [ + frequentlyEmojisList[0], + {...smileEmoji, count: 2, lastUpdatedAt: currentTime}, + ...frequentlyEmojisList.slice(1, -1), + ]; + expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); + }); + }); + + it('should sorted descending by count and lastUpdatedAt for multiple emoji added', () => { + const smileEmoji = {code: '😄', name: 'smile'}; + const zzzEmoji = {code: '💤', name: 'zzz'}; + const impEmoji = {code: '👿', name: 'imp'}; + const frequentlyEmojisList = [ + { + code: '😠', name: 'angry', count: 3, lastUpdatedAt: 5, + }, + { + code: '👋', name: 'wave', count: 2, lastUpdatedAt: 4, + }, + {...zzzEmoji, count: 2, lastUpdatedAt: 3}, + { + code: '💯', name: '100', count: 1, lastUpdatedAt: 2, + }, + {...smileEmoji, count: 1, lastUpdatedAt: 1}, + ]; + Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); + + return waitForPromisesToResolve() + .then(() => { + const currentTime = moment().unix(); + const newEmoji = [smileEmoji, zzzEmoji, impEmoji]; + EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + + const expectedFrequentlyEmojisList = [ + {...zzzEmoji, count: 3, lastUpdatedAt: currentTime}, + frequentlyEmojisList[0], + {...smileEmoji, count: 2, lastUpdatedAt: currentTime}, + frequentlyEmojisList[1], + {...impEmoji, count: 1, lastUpdatedAt: currentTime}, + frequentlyEmojisList[3], + ]; + expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); + }); + }); + + it('if the list is full, should replaced n least used emoji from the list with the n new emoji', () => { + const smileEmoji = {code: '😄', name: 'smile'}; + const zzzEmoji = {code: '💤', name: 'zzz'}; + const impEmoji = {code: '👿', name: 'imp'}; + const bookEmoji = {code: '📚', name: 'books'}; + + // Given the existing full (24 items) frequently used emojis list + const frequentlyEmojisList = [ + { + code: '😠', name: 'angry', count: 3, lastUpdatedAt: 24, + }, + { + code: '👋', name: 'wave', count: 3, lastUpdatedAt: 23, + }, + { + code: '😡', name: 'rage', count: 3, lastUpdatedAt: 22, + }, + { + code: '😤', name: 'triumph', count: 3, lastUpdatedAt: 21, + }, + { + code: '🥱', name: 'yawning_face', count: 3, lastUpdatedAt: 20, + }, + { + code: '😫', name: 'tired_face', count: 3, lastUpdatedAt: 19, + }, + { + code: '😩', name: 'weary', count: 3, lastUpdatedAt: 18, + }, + { + code: '😓', name: 'sweat', count: 3, lastUpdatedAt: 17, + }, + { + code: '😞', name: 'disappointed', count: 3, lastUpdatedAt: 16, + }, + { + code: '😣', name: 'persevere', count: 3, lastUpdatedAt: 15, + }, + { + code: '😖', name: 'confounded', count: 3, lastUpdatedAt: 14, + }, + { + code: '👶', name: 'baby', count: 3, lastUpdatedAt: 13, + }, + { + code: '👄', name: 'lips', count: 3, lastUpdatedAt: 12, + }, + { + code: '🐶', name: 'dog', count: 3, lastUpdatedAt: 11, + }, + { + code: '🦮', name: 'guide_dog', count: 3, lastUpdatedAt: 10, + }, + { + code: '🐱', name: 'cat', count: 3, lastUpdatedAt: 9, + }, + { + code: '🐈‍⬛', name: 'black_cat', count: 3, lastUpdatedAt: 8, + }, + { + code: '🕞', name: 'clock330', count: 3, lastUpdatedAt: 7, + }, + { + code: '🥎', name: 'softball', count: 3, lastUpdatedAt: 6, + }, + { + code: '🏀', name: 'basketball', count: 3, lastUpdatedAt: 5, + }, + { + code: '📟', name: 'pager', count: 3, lastUpdatedAt: 4, + }, + { + code: '🎬', name: 'clapper', count: 3, lastUpdatedAt: 3, + }, + { + code: '📺', name: 'tv', count: 3, lastUpdatedAt: 2, + }, + {...bookEmoji, count: 3, lastUpdatedAt: 1}, + ]; + Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); + + return waitForPromisesToResolve() + .then(() => { + // When add n (3) new emojis + const currentTime = moment().unix(); + const newEmoji = [bookEmoji, smileEmoji, zzzEmoji, impEmoji]; + EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + + // Then n least used emojis from the list should be replaced + const expectedFrequentlyEmojisList = [ + {...bookEmoji, count: 4, lastUpdatedAt: currentTime}, + ...frequentlyEmojisList.slice(0, (-1 - (newEmoji.length - 1))), + ..._.map(newEmoji.slice(1), e => ({...e, count: 1, lastUpdatedAt: currentTime})), + ]; + expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); + }); + }); + }); }); From 9b780a536b578c3614b9fcddd4e5e2c6c89e50ff Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 6 May 2023 18:07:13 +0800 Subject: [PATCH 06/10] fix typo --- tests/unit/EmojiTest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/EmojiTest.js b/tests/unit/EmojiTest.js index d9f8edac401b..a30e8506efd8 100644 --- a/tests/unit/EmojiTest.js +++ b/tests/unit/EmojiTest.js @@ -193,8 +193,8 @@ describe('EmojiTest', () => { const newEmoji = [smileEmoji]; EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); - const expectedSmileEmoj = {...smileEmoji, count: 1, lastUpdatedAt: currentTime}; - const expectedFrequentlyEmojisList = [...frequentlyEmojisList, expectedSmileEmoj]; + const expectedSmileEmoji = {...smileEmoji, count: 1, lastUpdatedAt: currentTime}; + const expectedFrequentlyEmojisList = [...frequentlyEmojisList, expectedSmileEmoji]; expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); }); }); From 3490d46872e011cd50ded6ae98867dfb3dcda207 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 6 May 2023 18:15:57 +0800 Subject: [PATCH 07/10] add comment --- tests/unit/EmojiTest.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/unit/EmojiTest.js b/tests/unit/EmojiTest.js index a30e8506efd8..dc6debc93ddc 100644 --- a/tests/unit/EmojiTest.js +++ b/tests/unit/EmojiTest.js @@ -170,6 +170,7 @@ describe('EmojiTest', () => { }); it('should put a less frequent and recent used emoji behind', () => { + // Given an existing frequently used emojis list with count > 1 const frequentlyEmojisList = [ { code: '👋', name: 'wave', count: 2, lastUpdatedAt: 4, @@ -188,11 +189,13 @@ describe('EmojiTest', () => { return waitForPromisesToResolve() .then(() => { + // When add a new emoji const currentTime = moment().unix(); const smileEmoji = {code: '😄', name: 'smile'}; const newEmoji = [smileEmoji]; EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + // Then the new emoji should be at the last item of the list const expectedSmileEmoji = {...smileEmoji, count: 1, lastUpdatedAt: currentTime}; const expectedFrequentlyEmojisList = [...frequentlyEmojisList, expectedSmileEmoji]; expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); @@ -200,6 +203,7 @@ describe('EmojiTest', () => { }); it('should put more frequent and recent used emoji to the front', () => { + // Given an existing frequently used emojis list const smileEmoji = {code: '😄', name: 'smile'}; const frequentlyEmojisList = [ { @@ -220,10 +224,12 @@ describe('EmojiTest', () => { return waitForPromisesToResolve() .then(() => { + // When add an emoji that exists in the list const currentTime = moment().unix(); const newEmoji = [smileEmoji]; EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + // Then the count should be increased and put into the very front of the other emoji within the same count const expectedFrequentlyEmojisList = [ frequentlyEmojisList[0], {...smileEmoji, count: 2, lastUpdatedAt: currentTime}, @@ -234,6 +240,7 @@ describe('EmojiTest', () => { }); it('should sorted descending by count and lastUpdatedAt for multiple emoji added', () => { + // Given an existing frequently used emojis list const smileEmoji = {code: '😄', name: 'smile'}; const zzzEmoji = {code: '💤', name: 'zzz'}; const impEmoji = {code: '👿', name: 'imp'}; @@ -254,10 +261,12 @@ describe('EmojiTest', () => { return waitForPromisesToResolve() .then(() => { + // When add multiple emojis that either exist or not exist in the list const currentTime = moment().unix(); const newEmoji = [smileEmoji, zzzEmoji, impEmoji]; EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + // Then the count should be increased for existing emoji and sorted descending by count and lastUpdatedAt const expectedFrequentlyEmojisList = [ {...zzzEmoji, count: 3, lastUpdatedAt: currentTime}, frequentlyEmojisList[0], @@ -271,12 +280,11 @@ describe('EmojiTest', () => { }); it('if the list is full, should replaced n least used emoji from the list with the n new emoji', () => { + // Given an existing full (24 items) frequently used emojis list const smileEmoji = {code: '😄', name: 'smile'}; const zzzEmoji = {code: '💤', name: 'zzz'}; const impEmoji = {code: '👿', name: 'imp'}; const bookEmoji = {code: '📚', name: 'books'}; - - // Given the existing full (24 items) frequently used emojis list const frequentlyEmojisList = [ { code: '😠', name: 'angry', count: 3, lastUpdatedAt: 24, From a414ef06049c7b1b06576cf49ecc4051360ef08e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 8 May 2023 11:52:49 +0800 Subject: [PATCH 08/10] use the old logic back but with looping --- src/libs/EmojiUtils.js | 56 ++++++++++++++--------------------------- tests/unit/EmojiTest.js | 14 ++++++----- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index 0784bcfccd7f..fef3503b7fbb 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -1,9 +1,5 @@ import _ from 'underscore'; -import lodashSumBy from 'lodash/sumBy'; -import lodashMaxBy from 'lodash/maxBy'; import lodashOrderBy from 'lodash/orderBy'; -import lodashIntersectionBy from 'lodash/intersectionBy'; -import lodashDifferenceBy from 'lodash/differenceBy'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; @@ -173,43 +169,29 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis) { * @param {Object|Object[]} newEmoji */ function addToFrequentlyUsedEmojis(newEmoji) { - const currentTimestamp = moment().unix(); - - // Get unique emojis array with counts for every emoji, these emojis are usually extracted from copy/pasted comments - const uniqueEmojisWithCount = _.chain([].concat(newEmoji)) - .groupBy('name') - .map(values => ({ - ...values[0], - count: values.length, - lastUpdatedAt: currentTimestamp, - })) - .value(); - - // Concat uniqueEmojisWithCount with the frequentlyUsedEmojis just to sum the count and update the lastUpdatedAt - const mergedEmojisWithFrequent = _.chain(uniqueEmojisWithCount) - .concat(frequentlyUsedEmojis) - .groupBy('name') - .map(groupedEmojiList => ({ - ...groupedEmojiList[0], - count: lodashSumBy(groupedEmojiList, 'count'), - lastUpdatedAt: lodashMaxBy(groupedEmojiList, 'lastUpdatedAt').lastUpdatedAt, - })) - .value(); + let frequentEmojiList = [...frequentlyUsedEmojis]; - const maxFrequentEmojiCount = (CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW) - uniqueEmojisWithCount.length; + const maxFrequentEmojiCount = (CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW) - 1; + const currentTimestamp = moment().unix(); + _.each([].concat(newEmoji), (emoji) => { + let currentEmojiCount = 1; + const emojiIndex = _.findIndex(frequentEmojiList, e => e.code === emoji.code); + if (emojiIndex >= 0) { + currentEmojiCount = frequentEmojiList[emojiIndex].count + 1; + frequentEmojiList.splice(emojiIndex, 1); + } + const updatedEmoji = {...emoji, ...{count: currentEmojiCount, lastUpdatedAt: currentTimestamp}}; - // After we get the new count and lastUpdatedAt, take out the updated unique emojis from mergedEmojisWithFrequent - const emojisWithNewCount = lodashIntersectionBy(mergedEmojisWithFrequent, uniqueEmojisWithCount, 'name'); - const frequentEmojisList = lodashDifferenceBy(mergedEmojisWithFrequent, emojisWithNewCount, 'name'); + // We want to make sure the current emoji is added to the list + // Hence, we take one less than the current frequent used emojis + frequentEmojiList = frequentEmojiList.slice(0, maxFrequentEmojiCount); + frequentEmojiList.push(updatedEmoji); - // Take the first maxFrequentEmojiCount items, push back the emojisWithNewCount, and sort the list by count and lastUpdatedAt - const frequentEmojisListOrdered = lodashOrderBy( - [...frequentEmojisList.slice(0, maxFrequentEmojiCount), ...emojisWithNewCount], - ['count', 'lastUpdatedAt'], - ['desc', 'desc'], - ); + // Sort the list after adding a new emoji to the frequent used emojis list + frequentEmojiList = lodashOrderBy(frequentEmojiList, ['count', 'lastUpdatedAt'], ['desc', 'desc']); + }); - User.updateFrequentlyUsedEmojis(frequentEmojisListOrdered); + User.updateFrequentlyUsedEmojis(frequentEmojiList); } /** diff --git a/tests/unit/EmojiTest.js b/tests/unit/EmojiTest.js index dc6debc93ddc..6f68d6c30633 100644 --- a/tests/unit/EmojiTest.js +++ b/tests/unit/EmojiTest.js @@ -7,6 +7,7 @@ import ONYXKEYS from '../../src/ONYXKEYS'; import * as User from '../../src/libs/actions/User'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import * as TestHelper from '../utils/TestHelper'; +import CONST from '../../src/CONST'; describe('EmojiTest', () => { it('matches all the emojis in the list', () => { @@ -279,7 +280,7 @@ describe('EmojiTest', () => { }); }); - it('if the list is full, should replaced n least used emoji from the list with the n new emoji', () => { + it('make sure the most recent new emoji is added to the list even it is full with count > 1', () => { // Given an existing full (24 items) frequently used emojis list const smileEmoji = {code: '😄', name: 'smile'}; const zzzEmoji = {code: '💤', name: 'zzz'}; @@ -357,20 +358,21 @@ describe('EmojiTest', () => { }, {...bookEmoji, count: 3, lastUpdatedAt: 1}, ]; + expect(frequentlyEmojisList.length).toBe(CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW); Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); return waitForPromisesToResolve() .then(() => { - // When add n (3) new emojis + // When add new emojis const currentTime = moment().unix(); - const newEmoji = [bookEmoji, smileEmoji, zzzEmoji, impEmoji]; + const newEmoji = [bookEmoji, smileEmoji, zzzEmoji, impEmoji, smileEmoji]; EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); - // Then n least used emojis from the list should be replaced + // Then the last emojis from the list should be replaced with the most recent new emoji (smile) const expectedFrequentlyEmojisList = [ {...bookEmoji, count: 4, lastUpdatedAt: currentTime}, - ...frequentlyEmojisList.slice(0, (-1 - (newEmoji.length - 1))), - ..._.map(newEmoji.slice(1), e => ({...e, count: 1, lastUpdatedAt: currentTime})), + ...frequentlyEmojisList.slice(0, -2), + {...smileEmoji, count: 1, lastUpdatedAt: currentTime}, ]; expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); }); From c277e87245b0f47e257356356d0608b9e8013d94 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 9 May 2023 00:02:55 +0800 Subject: [PATCH 09/10] using js native sort as lodash is much slower --- src/libs/EmojiUtils.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index fef3503b7fbb..511bdc0756cd 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -1,5 +1,4 @@ import _ from 'underscore'; -import lodashOrderBy from 'lodash/orderBy'; import moment from 'moment'; import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; @@ -187,8 +186,8 @@ function addToFrequentlyUsedEmojis(newEmoji) { frequentEmojiList = frequentEmojiList.slice(0, maxFrequentEmojiCount); frequentEmojiList.push(updatedEmoji); - // Sort the list after adding a new emoji to the frequent used emojis list - frequentEmojiList = lodashOrderBy(frequentEmojiList, ['count', 'lastUpdatedAt'], ['desc', 'desc']); + // Sort the list by count and lastUpdatedAt in descending order + frequentEmojiList.sort((a, b) => b.count - a.count || b.lastUpdatedAt - a.lastUpdatedAt); }); User.updateFrequentlyUsedEmojis(frequentEmojiList); From 4c8af675a1829c76a14de49cc9585a5c459216df Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 11 May 2023 12:58:42 +0800 Subject: [PATCH 10/10] prettier --- tests/unit/EmojiTest.js | 284 ++++++++++++++++++++++++++-------------- 1 file changed, 189 insertions(+), 95 deletions(-) diff --git a/tests/unit/EmojiTest.js b/tests/unit/EmojiTest.js index 39a90e7b3ccd..66a21d124cb3 100644 --- a/tests/unit/EmojiTest.js +++ b/tests/unit/EmojiTest.js @@ -166,33 +166,44 @@ describe('EmojiTest', () => { // Given an existing frequently used emojis list with count > 1 const frequentlyEmojisList = [ { - code: '👋', name: 'wave', count: 2, lastUpdatedAt: 4, + code: '👋', + name: 'wave', + count: 2, + lastUpdatedAt: 4, }, { - code: '💤', name: 'zzz', count: 2, lastUpdatedAt: 3, + code: '💤', + name: 'zzz', + count: 2, + lastUpdatedAt: 3, }, { - code: '💯', name: '100', count: 2, lastUpdatedAt: 2, + code: '💯', + name: '100', + count: 2, + lastUpdatedAt: 2, }, { - code: '👿', name: 'imp', count: 2, lastUpdatedAt: 1, + code: '👿', + name: 'imp', + count: 2, + lastUpdatedAt: 1, }, ]; Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); - return waitForPromisesToResolve() - .then(() => { - // When add a new emoji - const currentTime = moment().unix(); - const smileEmoji = {code: '😄', name: 'smile'}; - const newEmoji = [smileEmoji]; - EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); - - // Then the new emoji should be at the last item of the list - const expectedSmileEmoji = {...smileEmoji, count: 1, lastUpdatedAt: currentTime}; - const expectedFrequentlyEmojisList = [...frequentlyEmojisList, expectedSmileEmoji]; - expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); - }); + return waitForPromisesToResolve().then(() => { + // When add a new emoji + const currentTime = moment().unix(); + const smileEmoji = {code: '😄', name: 'smile'}; + const newEmoji = [smileEmoji]; + EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + + // Then the new emoji should be at the last item of the list + const expectedSmileEmoji = {...smileEmoji, count: 1, lastUpdatedAt: currentTime}; + const expectedFrequentlyEmojisList = [...frequentlyEmojisList, expectedSmileEmoji]; + expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); + }); }); it('should put more frequent and recent used emoji to the front', () => { @@ -200,36 +211,43 @@ describe('EmojiTest', () => { const smileEmoji = {code: '😄', name: 'smile'}; const frequentlyEmojisList = [ { - code: '😠', name: 'angry', count: 3, lastUpdatedAt: 5, + code: '😠', + name: 'angry', + count: 3, + lastUpdatedAt: 5, }, { - code: '👋', name: 'wave', count: 2, lastUpdatedAt: 4, + code: '👋', + name: 'wave', + count: 2, + lastUpdatedAt: 4, }, { - code: '💤', name: 'zzz', count: 2, lastUpdatedAt: 3, + code: '💤', + name: 'zzz', + count: 2, + lastUpdatedAt: 3, }, { - code: '💯', name: '100', count: 1, lastUpdatedAt: 2, + code: '💯', + name: '100', + count: 1, + lastUpdatedAt: 2, }, {...smileEmoji, count: 1, lastUpdatedAt: 1}, ]; Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); - return waitForPromisesToResolve() - .then(() => { - // When add an emoji that exists in the list - const currentTime = moment().unix(); - const newEmoji = [smileEmoji]; - EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); - - // Then the count should be increased and put into the very front of the other emoji within the same count - const expectedFrequentlyEmojisList = [ - frequentlyEmojisList[0], - {...smileEmoji, count: 2, lastUpdatedAt: currentTime}, - ...frequentlyEmojisList.slice(1, -1), - ]; - expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); - }); + return waitForPromisesToResolve().then(() => { + // When add an emoji that exists in the list + const currentTime = moment().unix(); + const newEmoji = [smileEmoji]; + EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + + // Then the count should be increased and put into the very front of the other emoji within the same count + const expectedFrequentlyEmojisList = [frequentlyEmojisList[0], {...smileEmoji, count: 2, lastUpdatedAt: currentTime}, ...frequentlyEmojisList.slice(1, -1)]; + expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); + }); }); it('should sorted descending by count and lastUpdatedAt for multiple emoji added', () => { @@ -239,37 +257,45 @@ describe('EmojiTest', () => { const impEmoji = {code: '👿', name: 'imp'}; const frequentlyEmojisList = [ { - code: '😠', name: 'angry', count: 3, lastUpdatedAt: 5, + code: '😠', + name: 'angry', + count: 3, + lastUpdatedAt: 5, }, { - code: '👋', name: 'wave', count: 2, lastUpdatedAt: 4, + code: '👋', + name: 'wave', + count: 2, + lastUpdatedAt: 4, }, {...zzzEmoji, count: 2, lastUpdatedAt: 3}, { - code: '💯', name: '100', count: 1, lastUpdatedAt: 2, + code: '💯', + name: '100', + count: 1, + lastUpdatedAt: 2, }, {...smileEmoji, count: 1, lastUpdatedAt: 1}, ]; Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); - return waitForPromisesToResolve() - .then(() => { - // When add multiple emojis that either exist or not exist in the list - const currentTime = moment().unix(); - const newEmoji = [smileEmoji, zzzEmoji, impEmoji]; - EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); - - // Then the count should be increased for existing emoji and sorted descending by count and lastUpdatedAt - const expectedFrequentlyEmojisList = [ - {...zzzEmoji, count: 3, lastUpdatedAt: currentTime}, - frequentlyEmojisList[0], - {...smileEmoji, count: 2, lastUpdatedAt: currentTime}, - frequentlyEmojisList[1], - {...impEmoji, count: 1, lastUpdatedAt: currentTime}, - frequentlyEmojisList[3], - ]; - expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); - }); + return waitForPromisesToResolve().then(() => { + // When add multiple emojis that either exist or not exist in the list + const currentTime = moment().unix(); + const newEmoji = [smileEmoji, zzzEmoji, impEmoji]; + EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + + // Then the count should be increased for existing emoji and sorted descending by count and lastUpdatedAt + const expectedFrequentlyEmojisList = [ + {...zzzEmoji, count: 3, lastUpdatedAt: currentTime}, + frequentlyEmojisList[0], + {...smileEmoji, count: 2, lastUpdatedAt: currentTime}, + frequentlyEmojisList[1], + {...impEmoji, count: 1, lastUpdatedAt: currentTime}, + frequentlyEmojisList[3], + ]; + expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); + }); }); it('make sure the most recent new emoji is added to the list even it is full with count > 1', () => { @@ -280,94 +306,162 @@ describe('EmojiTest', () => { const bookEmoji = {code: '📚', name: 'books'}; const frequentlyEmojisList = [ { - code: '😠', name: 'angry', count: 3, lastUpdatedAt: 24, + code: '😠', + name: 'angry', + count: 3, + lastUpdatedAt: 24, }, { - code: '👋', name: 'wave', count: 3, lastUpdatedAt: 23, + code: '👋', + name: 'wave', + count: 3, + lastUpdatedAt: 23, }, { - code: '😡', name: 'rage', count: 3, lastUpdatedAt: 22, + code: '😡', + name: 'rage', + count: 3, + lastUpdatedAt: 22, }, { - code: '😤', name: 'triumph', count: 3, lastUpdatedAt: 21, + code: '😤', + name: 'triumph', + count: 3, + lastUpdatedAt: 21, }, { - code: '🥱', name: 'yawning_face', count: 3, lastUpdatedAt: 20, + code: '🥱', + name: 'yawning_face', + count: 3, + lastUpdatedAt: 20, }, { - code: '😫', name: 'tired_face', count: 3, lastUpdatedAt: 19, + code: '😫', + name: 'tired_face', + count: 3, + lastUpdatedAt: 19, }, { - code: '😩', name: 'weary', count: 3, lastUpdatedAt: 18, + code: '😩', + name: 'weary', + count: 3, + lastUpdatedAt: 18, }, { - code: '😓', name: 'sweat', count: 3, lastUpdatedAt: 17, + code: '😓', + name: 'sweat', + count: 3, + lastUpdatedAt: 17, }, { - code: '😞', name: 'disappointed', count: 3, lastUpdatedAt: 16, + code: '😞', + name: 'disappointed', + count: 3, + lastUpdatedAt: 16, }, { - code: '😣', name: 'persevere', count: 3, lastUpdatedAt: 15, + code: '😣', + name: 'persevere', + count: 3, + lastUpdatedAt: 15, }, { - code: '😖', name: 'confounded', count: 3, lastUpdatedAt: 14, + code: '😖', + name: 'confounded', + count: 3, + lastUpdatedAt: 14, }, { - code: '👶', name: 'baby', count: 3, lastUpdatedAt: 13, + code: '👶', + name: 'baby', + count: 3, + lastUpdatedAt: 13, }, { - code: '👄', name: 'lips', count: 3, lastUpdatedAt: 12, + code: '👄', + name: 'lips', + count: 3, + lastUpdatedAt: 12, }, { - code: '🐶', name: 'dog', count: 3, lastUpdatedAt: 11, + code: '🐶', + name: 'dog', + count: 3, + lastUpdatedAt: 11, }, { - code: '🦮', name: 'guide_dog', count: 3, lastUpdatedAt: 10, + code: '🦮', + name: 'guide_dog', + count: 3, + lastUpdatedAt: 10, }, { - code: '🐱', name: 'cat', count: 3, lastUpdatedAt: 9, + code: '🐱', + name: 'cat', + count: 3, + lastUpdatedAt: 9, }, { - code: '🐈‍⬛', name: 'black_cat', count: 3, lastUpdatedAt: 8, + code: '🐈‍⬛', + name: 'black_cat', + count: 3, + lastUpdatedAt: 8, }, { - code: '🕞', name: 'clock330', count: 3, lastUpdatedAt: 7, + code: '🕞', + name: 'clock330', + count: 3, + lastUpdatedAt: 7, }, { - code: '🥎', name: 'softball', count: 3, lastUpdatedAt: 6, + code: '🥎', + name: 'softball', + count: 3, + lastUpdatedAt: 6, }, { - code: '🏀', name: 'basketball', count: 3, lastUpdatedAt: 5, + code: '🏀', + name: 'basketball', + count: 3, + lastUpdatedAt: 5, }, { - code: '📟', name: 'pager', count: 3, lastUpdatedAt: 4, + code: '📟', + name: 'pager', + count: 3, + lastUpdatedAt: 4, }, { - code: '🎬', name: 'clapper', count: 3, lastUpdatedAt: 3, + code: '🎬', + name: 'clapper', + count: 3, + lastUpdatedAt: 3, }, { - code: '📺', name: 'tv', count: 3, lastUpdatedAt: 2, + code: '📺', + name: 'tv', + count: 3, + lastUpdatedAt: 2, }, {...bookEmoji, count: 3, lastUpdatedAt: 1}, ]; expect(frequentlyEmojisList.length).toBe(CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW); Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); - return waitForPromisesToResolve() - .then(() => { - // When add new emojis - const currentTime = moment().unix(); - const newEmoji = [bookEmoji, smileEmoji, zzzEmoji, impEmoji, smileEmoji]; - EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); - - // Then the last emojis from the list should be replaced with the most recent new emoji (smile) - const expectedFrequentlyEmojisList = [ - {...bookEmoji, count: 4, lastUpdatedAt: currentTime}, - ...frequentlyEmojisList.slice(0, -2), - {...smileEmoji, count: 1, lastUpdatedAt: currentTime}, - ]; - expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); - }); + return waitForPromisesToResolve().then(() => { + // When add new emojis + const currentTime = moment().unix(); + const newEmoji = [bookEmoji, smileEmoji, zzzEmoji, impEmoji, smileEmoji]; + EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + + // Then the last emojis from the list should be replaced with the most recent new emoji (smile) + const expectedFrequentlyEmojisList = [ + {...bookEmoji, count: 4, lastUpdatedAt: currentTime}, + ...frequentlyEmojisList.slice(0, -2), + {...smileEmoji, count: 1, lastUpdatedAt: currentTime}, + ]; + expect(spy).toBeCalledWith(expectedFrequentlyEmojisList); + }); }); }); });