App: [hold for app #12854] [$2000] Shows incorrect emoji while combining the manual and emoji picker selected emoji within the text

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Action Performed:

  1. Load new.expensify.com.
  2. Open a chat.
  3. type Te:smile :st in the text bar.
  4. remove the space so the smile emoji renders inside of the word test. leave your cursor flush with the emoji.
  5. click the emoji picker.
  6. select an emoji.
  7. confirm that the new emoji loads in between the two question mark characters. This is the bug.

Expected Result:

Should show both emoji adjacently after selecting the second one.

Actual Result:

Shows a weird emoji, and removes the original emoji, incorrectly.

Workaround:

Select emojis via the emoji picker.

Platform:

Where is this issue occurring?

  • Web

Version Number: v1.2.32-1 Reproducible in staging?: y Reproducible in production?: y Email or phone of affected tester (no customers): Logs: https://stackoverflow.com/c/expensify/questions/4856 Notes/Photos/Videos: 7c67d298-274a-4ea8-a781-e28384f08007.webm https://user-images.githubusercontent.com/43996225/204145304-bf1f2078-3b3f-46c5-9ac0-26b4d8bcf01e.mp4

Expensify/Expensify Issue URL: Issue reported by: @Pujan92 Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1669391872652859

View all open jobs on GitHub

https://user-images.githubusercontent.com/43996225/204145304-bf1f2078-3b3f-46c5-9ac0-26b4d8bcf01e.mp4

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~016d120322c0f448f0
  • Upwork Job ID: 1597336781605519360
  • Last Price Increase: 2023-04-17
Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~016d120322c0f448f0
  • Upwork Job ID: 1597336781605519360
  • Last Price Increase: 2023-04-17

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 138 (112 by maintainers)

Most upvoted comments

@joekaufmanexpensify Yes, that’s right and by the way the RN PR has been merged already.

PS: please tag me next time for faster responses

Removed Help Wanted because job is on hold, please add back when it’s off hold

In that case, we should wait for this (https://github.com/Expensify/App/issues/12854) to be fixed. That will fix the controlled selection issue on Android and iOS that prevent us from testing this GH issue on native.

Updated Proposal (1)

As @s77rt mentioned, my proposal does not work for copy/paste case (thanks for mentioning the potential issue). So, here is my new proposal. It is very similar to the previous one.

Nothing changes for replaceEmojis function as we still need the emoji position. I only change the name to lastEmojiEnd.

function replaceEmojis(text) {
    let newText = text;
+   let lastEmojiEnd = 0;
-   const emojiData = text.match(CONST.REGEX.EMOJI_NAME);
+   const emojiData = text.match(CONST.REGEX.EMOJI_NAME) || [];
-   if (!emojiData || emojiData.length === 0) {
-       return text;
-   }
    for (let i = 0; i < emojiData.length; i++) {
        const checkEmoji = emojisTrie.search(emojiData[i].slice(1, -1));
        if (checkEmoji && checkEmoji.metaData.code) {
            let emojiReplacement = checkEmoji.metaData.code;

            // 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.
            if (i === emojiData.length - 1 && text.endsWith(emojiData[i])) {
                emojiReplacement += ' ';
            }
-           newText = newText.replace(emojiData[i], emojiReplacement);
+           newText = newText.replace(emojiData[i], (match, offset) => {
+               lastEmojiEnd= offset + emojiReplacement.length;
+               return emojiReplacement
+           });
        }
    }
+   return {newText, lastEmojiEnd};
-   return newText;
}

Then, to find the correct selection position, we need to get from the max value between lastEmojiEnd and the length of the new text added + previous selection end value.

-const newComment = EmojiUtils.replaceEmojis(comment);
+const {newText: newComment, lastEmojiEnd} = EmojiUtils.replaceEmojis(comment);
this.setState((prevState) => {
    const newState = {
        isCommentEmpty: !!newComment.match(/^(\s|`)*$/),
        value: newComment,
    };
    if (comment !== newComment) {
-       const remainder = prevState.value.slice(prevState.selection.end).length;
+       const lengthDiff = newComment.length - prevState.value.length;
+       const newPos = Math.max(prevState.selection.end + lengthDiff, lastEmojiEnd);
        newState.selection = {
-           start: newComment.length - remainder,
-           end: newComment.length - remainder,
+           start: newPos,
+           end: newPos,
        }
    }
    return newState;
});

Result

https://user-images.githubusercontent.com/50919443/205490250-5737ecf7-2711-4a40-8b3a-4e96596b297e.mp4

@sobitneupane I see now thanks for clarification!

@s77rt Thanks for your proposal..

Can you please explain compensation in your proposal? Also your proposal won’t do the job in all conditions. For example: test on this one “Hello ever: smile:yone”

@Pujan92 Thanks for your proposal.

The reason you stated for this issue seems valid. And your solution might do the job. But it is does not look good performance wise.

Can you think any other ways to solve this issue?