From d1a45bfb0770fb7e2a109683c4da40d9d134b93c Mon Sep 17 00:00:00 2001 From: paulnjs Date: Tue, 16 Dec 2025 23:38:21 +0700 Subject: [PATCH 01/13] fix: Global create emoji is overlapping the message with markdown --- src/components/EmojiWithTooltip/index.ios.tsx | 5 ++++- src/components/EmojiWithTooltip/types.ts | 1 + .../HTMLRenderers/EmojiRenderer.tsx | 1 + .../home/report/comment/TextCommentFragment.tsx | 15 +++++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/EmojiWithTooltip/index.ios.tsx b/src/components/EmojiWithTooltip/index.ios.tsx index b5ecfc43c250..aec3391f5df1 100644 --- a/src/components/EmojiWithTooltip/index.ios.tsx +++ b/src/components/EmojiWithTooltip/index.ios.tsx @@ -3,9 +3,12 @@ import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; import type EmojiWithTooltipProps from './types'; -function EmojiWithTooltip({emojiCode, style = {}, isMedium = false}: EmojiWithTooltipProps) { +function EmojiWithTooltip({emojiCode, style = {}, isMedium = false, oneLine = false}: EmojiWithTooltipProps) { const styles = useThemeStyles(); const isCustomEmoji = emojiCode === '\uE100'; + if (oneLine) { + return {emojiCode}; + } return isMedium ? ( diff --git a/src/components/EmojiWithTooltip/types.ts b/src/components/EmojiWithTooltip/types.ts index 1e7993198e98..850eff5a5344 100644 --- a/src/components/EmojiWithTooltip/types.ts +++ b/src/components/EmojiWithTooltip/types.ts @@ -4,6 +4,7 @@ type EmojiWithTooltipProps = { emojiCode: string; style?: StyleProp; isMedium?: boolean; + oneLine?: boolean; }; export default EmojiWithTooltipProps; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx index 33790b2a10e7..d017a478af6b 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx @@ -19,6 +19,7 @@ function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps containsOnlyCustomEmojiUtil(text), [text]); const containsEmojis = CONST.REGEX.ALL_EMOJIS.test(text ?? ''); + if (!shouldRenderAsText(html, text ?? '') && !(containsOnlyEmojis && styleAsDeleted)) { const editedTag = fragment?.isEdited ? `` : ''; // We need to replace the space at the beginning of each line with   @@ -101,6 +102,20 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM htmlWithTag = adjustExpensifyLinksForEnv(getHtmlWithAttachmentID(htmlWithTag, reportActionID)); + const lines = htmlContent.split(//i); + function isSingleEmojiLine(line: string) { + const trimmed = line.replace(//gi, '').trim(); + return /^.*<\/emoji>$/.test(trimmed); + } + const processedLines = lines.map((line) => { + if (isSingleEmojiLine(line)) { + return line.replace('', ''); + } + return line.replace('', ''); + }); + + htmlContent = processedLines.join('
'); + return ( Date: Tue, 16 Dec 2025 23:45:08 +0700 Subject: [PATCH 02/13] update code --- .../HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx index d017a478af6b..573b172505c5 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx @@ -19,7 +19,7 @@ function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps Date: Tue, 16 Dec 2025 23:50:47 +0700 Subject: [PATCH 03/13] fix lint --- src/pages/home/report/comment/TextCommentFragment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index 6e1fdbd73c79..54374e3174d5 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -104,7 +104,7 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM const lines = htmlContent.split(//i); function isSingleEmojiLine(line: string) { - const trimmed = line.replace(//gi, '').trim(); + const trimmed = line.replaceAll(//gi, '').trim(); return /^.*<\/emoji>$/.test(trimmed); } const processedLines = lines.map((line) => { From 3059588b97c76ee07dc732c8235ff7dc7d55dce4 Mon Sep 17 00:00:00 2001 From: paulnjs Date: Tue, 16 Dec 2025 23:59:19 +0700 Subject: [PATCH 04/13] update code --- cspell.json | 62 ++++++++++--------- .../HTMLRenderers/EmojiRenderer.tsx | 2 +- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/cspell.json b/cspell.json index 733dad28a69a..48f10818b164 100644 --- a/cspell.json +++ b/cspell.json @@ -3,6 +3,7 @@ "words": [ "--longpress", "Accelo", + "accountid", "achreimburse", "actool", "adbd", @@ -37,6 +38,7 @@ "approvalstatus", "appversion", "archivado", + "Areport", "ARGB", "armeabi", "armv7", @@ -51,7 +53,6 @@ "asar", "ASPAC", "assetlinks", - "accountid", "attributes.accountid", "attributes.reportid", "authorised", @@ -96,17 +97,17 @@ "buildscript", "Buildscript", "Bushwick", + "cacerts", "capitalone", "CAROOT", "Carta", - "cacerts", - "changeit", "ccache", "ccupload", "cdfbmo", "Certinia", "Certinia's", "CFPB", + "changeit", "chargeback", "Charleson", "Checkmark", @@ -132,6 +133,7 @@ "contenteditable", "copiloted", "copiloting", + "copyable", "Corpay", "Countertop", "CPPFLAGS", @@ -144,13 +146,12 @@ "customfield", "customise", "dateexported", + "deapex", + "deapexer", "debitamount", "deburr", "deburred", - "REJECTEDTRANSACTION", "Deel", - "deapex", - "deapexer", "deeplink", "deeplinked", "deeplinking", @@ -175,7 +176,9 @@ "Drycleaning", "DSYM", "dsyms", + "Dtype", "durationMillis", + "DYNAMICEXTERNAL", "e2edelta", "ecash", "ecconnrefused", @@ -215,12 +218,12 @@ "Expensable", "expensescount", "Expensi", - "Expensidev", "Expensicon", "Expensicons", "expensicorp", - "Expensifier", + "Expensidev", "EXPENSIDEV", + "Expensifier", "EXPENSIFYAPI", "expensifyhelp", "expensifylite", @@ -338,8 +341,8 @@ "keyvaluepairs", "KHTML", "killall", - "kilometres", "kilometre", + "kilometres", "Kort", "Kowalski", "Krasoń", @@ -397,6 +400,9 @@ "Mobasher", "mobiexpensifyg", "mobileprovision", + "moveElemsAttrsToGroup", + "moveGroupAttrsToElems", + "mple", "mswin", "msword", "mtrl", @@ -442,9 +448,9 @@ "Nonstore", "Nonupholstered", "noopener", + "noprompt", "noreferer", "noreferrer", - "noprompt", "nosymbol", "Noto", "NSQS", @@ -467,6 +473,7 @@ "OLDDOT", "onclosetag", "Oncorp", + "oneline", "oneteam", "oneui", "onfido", @@ -567,8 +574,10 @@ "REIMBURSER", "reimbursible", "Reimbursments", + "REJECTEDTRANSACTION", "remotedesktop", "remotesync", + "removeHiddenElems", "REPORTPREVIEW", "requestee", "Resawing", @@ -577,11 +586,12 @@ "retryable", "Reupholstery", "rideshare", - "rock", + "RNCORE", "RNFS", "rnmapbox", "RNTL", "RNVP", + "rock", "Roni", "Rosiclair", "rpartition", @@ -601,18 +611,19 @@ "Schengen", "Schiffli", "SCIM", - "sdkmanager", "scriptname", + "sdkmanager", "seamless", "Segoe", "seguiemj", + "Selec", "Sepa", "serveo", + "setuptools", "Sharees", "Sharons", "shellcheck", "shellenv", - "Skydo", "shipit", "shouldshowellipsis", "signingkey", @@ -620,21 +631,23 @@ "Signup", "simctl", "skip_codesigning", + "Skydo", "Slurper", "SMARTREPORT", "Smartscan", "soloader", "SONIFICATION", "Speedscope", + "Splittable", "Spotnana", "spreadsheetml", "srgb", "SSAE", + "stackoverflow", "startdate", "stdev", "stdlib", "storepass", - "stackoverflow", "STORYLANE", "strikethrough", "Strikethrough", @@ -742,6 +755,7 @@ "webrtc", "welldone", "Woohoo", + "Wooo", "Wordmark", "wordprocessingml", "worklet", @@ -781,21 +795,7 @@ "zoneinfo", "zxcv", "zxldvw", - "inputmethod", - "copyable", - "مثال", - "moveElemsAttrsToGroup", - "removeHiddenElems", - "moveGroupAttrsToElems", - "Dtype", - "Areport", - "mple", - "Selec", - "setuptools", - "DYNAMICEXTERNAL", - "RNCORE", - "Wooo", - "Splittable" + "مثال" ], "ignorePaths": [ "src/languages/de.ts", @@ -848,6 +848,8 @@ "web/snippets/gib.js", "tests/unit/hooks/useLetterAvatars.test.tsx" ], - "ignoreRegExpList": ["@assets/.*"], + "ignoreRegExpList": [ + "@assets/.*" + ], "useGitignore": true } diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx index 573b172505c5..b21770d2904f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx @@ -19,7 +19,7 @@ function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps Date: Wed, 17 Dec 2025 09:32:00 +0700 Subject: [PATCH 05/13] update code --- cspell.json | 4 +-- .../report/comment/TextCommentFragment.tsx | 28 +++++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/cspell.json b/cspell.json index 48f10818b164..38a995e9a222 100644 --- a/cspell.json +++ b/cspell.json @@ -848,8 +848,6 @@ "web/snippets/gib.js", "tests/unit/hooks/useLetterAvatars.test.tsx" ], - "ignoreRegExpList": [ - "@assets/.*" - ], + "ignoreRegExpList": ["@assets/.*"], "useGitignore": true } diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index 54374e3174d5..a2b6186c202a 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -91,7 +91,19 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM if (!htmlContent.includes('')) { htmlContent = Parser.replace(htmlContent, {filterRules: ['emoji'], shouldEscapeText: false}); } - htmlContent = Str.replaceAll(htmlContent, '', ''); + const lines = htmlContent.split(//i); + function isSingleEmojiLine(line: string) { + const trimmed = line.replaceAll(//gi, '').trim(); + return /^.*<\/emoji>$/.test(trimmed); + } + const processedLines = lines.map((line) => { + if (isSingleEmojiLine(line)) { + return line.replace('', ''); + } + return line.replace('', ''); + }); + + htmlContent = processedLines.join('
'); } let htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent; @@ -102,20 +114,6 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM htmlWithTag = adjustExpensifyLinksForEnv(getHtmlWithAttachmentID(htmlWithTag, reportActionID)); - const lines = htmlContent.split(//i); - function isSingleEmojiLine(line: string) { - const trimmed = line.replaceAll(//gi, '').trim(); - return /^.*<\/emoji>$/.test(trimmed); - } - const processedLines = lines.map((line) => { - if (isSingleEmojiLine(line)) { - return line.replace('', ''); - } - return line.replace('', ''); - }); - - htmlContent = processedLines.join('
'); - return ( Date: Wed, 17 Dec 2025 09:35:58 +0700 Subject: [PATCH 06/13] update code --- cspell.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cspell.json b/cspell.json index c39f0a2a2538..3b36c0acfa40 100644 --- a/cspell.json +++ b/cspell.json @@ -98,6 +98,7 @@ "Buildscript", "Bushwick", "BYOC", + "cacerts", "capitalone", "CAROOT", "Carta", @@ -848,6 +849,8 @@ "web/snippets/gib.js", "tests/unit/hooks/useLetterAvatars.test.tsx" ], - "ignoreRegExpList": ["@assets/.*"], + "ignoreRegExpList": [ + "@assets/.*" + ], "useGitignore": true } From 836c12b404d04127eef03dd763beef56056ed1a5 Mon Sep 17 00:00:00 2001 From: paulnjs Date: Wed, 17 Dec 2025 09:40:30 +0700 Subject: [PATCH 07/13] update cspell --- cspell.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cspell.json b/cspell.json index 3b36c0acfa40..aebf726b2023 100644 --- a/cspell.json +++ b/cspell.json @@ -849,8 +849,6 @@ "web/snippets/gib.js", "tests/unit/hooks/useLetterAvatars.test.tsx" ], - "ignoreRegExpList": [ - "@assets/.*" - ], + "ignoreRegExpList": ["@assets/.*"], "useGitignore": true } From 6132fa8066aff7901dd6db62722ef3482fab5fd7 Mon Sep 17 00:00:00 2001 From: paulnjs Date: Wed, 17 Dec 2025 09:52:47 +0700 Subject: [PATCH 08/13] fix react compile --- src/pages/home/report/comment/TextCommentFragment.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index 04228b73191d..9e5bd575a9fa 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -1,6 +1,6 @@ import {Str} from 'expensify-common'; import isEmpty from 'lodash/isEmpty'; -import React, {memo, useEffect, useMemo} from 'react'; +import React, {useEffect} from 'react'; import type {StyleProp, TextStyle} from 'react-native'; import Text from '@components/Text'; import ZeroWidthView from '@components/ZeroWidthView'; @@ -62,7 +62,7 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM const message = isEmpty(iouMessage) ? text : iouMessage; - const processedTextArray = useMemo(() => splitTextWithEmojis(message), [message]); + const processedTextArray = splitTextWithEmojis(message); useEffect(() => { Performance.markEnd(CONST.TIMING.SEND_MESSAGE, {message: text}); @@ -74,7 +74,7 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM // on native, we render it as text, not as html // on other device, only render it as text if the only difference is
tag const containsOnlyEmojis = containsOnlyEmojisUtil(text ?? ''); - const containsOnlyCustomEmoji = useMemo(() => containsOnlyCustomEmojiUtil(text), [text]); + const containsOnlyCustomEmoji = containsOnlyCustomEmojiUtil(text); const containsEmojis = CONST.REGEX.ALL_EMOJIS.test(text ?? ''); if (!shouldRenderAsText(html, text ?? '') && !(containsOnlyEmojis && styleAsDeleted)) { @@ -171,4 +171,4 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM ); } -export default memo(TextCommentFragment); +export default TextCommentFragment; From 31721e643f84b54501c6b82ee98648b02d1dc262 Mon Sep 17 00:00:00 2001 From: paulnjs Date: Wed, 17 Dec 2025 09:57:13 +0700 Subject: [PATCH 09/13] fix react compiler --- .../HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx index 198a4272c272..92ccc114388c 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx @@ -1,4 +1,3 @@ -import React, {useMemo} from 'react'; import type {TextStyle} from 'react-native'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import EmojiWithTooltip from '@components/EmojiWithTooltip'; @@ -6,7 +5,8 @@ import useThemeStyles from '@hooks/useThemeStyles'; function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps) { const styles = useThemeStyles(); - const style = useMemo(() => { + + const getStyle = (): TextStyle[] | null => { if ('islarge' in tnode.attributes) { return [styleProp as TextStyle, styles.onlyEmojisText]; } @@ -16,7 +16,9 @@ function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps Date: Fri, 26 Dec 2025 15:28:17 +0700 Subject: [PATCH 10/13] refactor code --- .../HTMLRenderers/EmojiRenderer.tsx | 18 +++++++----------- .../report/comment/TextCommentFragment.tsx | 1 - 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx index 92ccc114388c..9b22f2f9c12c 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx @@ -6,19 +6,15 @@ import useThemeStyles from '@hooks/useThemeStyles'; function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps) { const styles = useThemeStyles(); - const getStyle = (): TextStyle[] | null => { - if ('islarge' in tnode.attributes) { - return [styleProp as TextStyle, styles.onlyEmojisText]; - } + let style; + if ('islarge' in tnode.attributes) { + style = [styleProp as TextStyle, styles.onlyEmojisText]; + } - if ('ismedium' in tnode.attributes) { - return [styleProp as TextStyle, styles.emojisWithTextFontSize, styles.verticalAlignTopText]; - } + if ('ismedium' in tnode.attributes) { + style = [styleProp as TextStyle, styles.emojisWithTextFontSize, styles.verticalAlignTopText]; + } - return null; - }; - - const style = getStyle(); return ( ` : ''; // We need to replace the space at the beginning of each line with   From f691cd2f641a2cab08ebc3e6b8e335225b62e7e0 Mon Sep 17 00:00:00 2001 From: paulnjs Date: Fri, 26 Dec 2025 18:03:41 +0700 Subject: [PATCH 11/13] update code --- .../HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx index 9b22f2f9c12c..2622da978090 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx @@ -9,10 +9,10 @@ function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps Date: Mon, 29 Dec 2025 16:12:55 +0700 Subject: [PATCH 12/13] update code --- .../HTMLRenderers/EmojiRenderer.tsx | 1 + src/libs/EmojiUtils.tsx | 6 ++ .../report/comment/TextCommentFragment.tsx | 6 +- tests/unit/libs/EmojiUtilsTest.ts | 68 ++++++++++++++++++- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx index 2622da978090..2d6c08449aaf 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import type {TextStyle} from 'react-native'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import EmojiWithTooltip from '@components/EmojiWithTooltip'; diff --git a/src/libs/EmojiUtils.tsx b/src/libs/EmojiUtils.tsx index 315b51aabfca..00cd78a35dc3 100644 --- a/src/libs/EmojiUtils.tsx +++ b/src/libs/EmojiUtils.tsx @@ -662,6 +662,11 @@ function containsOnlyCustomEmoji(text?: string): boolean { return privateUseAreaRegex.test(text); } +function isSingleEmojiLine(line: string) { + const trimmed = line.replaceAll(//gi, '').trim(); + return /^.*<\/emoji>$/.test(trimmed); +} + export type {HeaderIndices, EmojiPickerList, EmojiPickerListItem}; export { @@ -689,4 +694,5 @@ export { containsCustomEmoji, containsOnlyCustomEmoji, processFrequentlyUsedEmojis, + isSingleEmojiLine, }; diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index 85c6fd152a45..b57a5303a9ef 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -11,7 +11,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import convertToLTR from '@libs/convertToLTR'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; -import {containsOnlyCustomEmoji as containsOnlyCustomEmojiUtil, containsOnlyEmojis as containsOnlyEmojisUtil, splitTextWithEmojis} from '@libs/EmojiUtils'; +import {containsOnlyCustomEmoji as containsOnlyCustomEmojiUtil, containsOnlyEmojis as containsOnlyEmojisUtil, isSingleEmojiLine, splitTextWithEmojis} from '@libs/EmojiUtils'; import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; import {getHtmlWithAttachmentID, getTextFromHtml} from '@libs/ReportActionsUtils'; @@ -91,10 +91,6 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM htmlContent = Parser.replace(htmlContent, {filterRules: ['emoji'], shouldEscapeText: false}); } const lines = htmlContent.split(//i); - function isSingleEmojiLine(line: string) { - const trimmed = line.replaceAll(//gi, '').trim(); - return /^.*<\/emoji>$/.test(trimmed); - } const processedLines = lines.map((line) => { if (isSingleEmojiLine(line)) { return line.replace('', ''); diff --git a/tests/unit/libs/EmojiUtilsTest.ts b/tests/unit/libs/EmojiUtilsTest.ts index f9e27ef92043..4f651d9fa1af 100644 --- a/tests/unit/libs/EmojiUtilsTest.ts +++ b/tests/unit/libs/EmojiUtilsTest.ts @@ -1,4 +1,4 @@ -import {processFrequentlyUsedEmojis} from '@libs/EmojiUtils'; +import {isSingleEmojiLine, processFrequentlyUsedEmojis} from '@libs/EmojiUtils'; import type {FrequentlyUsedEmoji} from '@src/types/onyx'; // Mock the Emojis module @@ -302,3 +302,69 @@ describe('processFrequentlyUsedEmojis', () => { expect(result.at(1)?.lastUpdatedAt).toBe(1500); }); }); + +describe('isSingleEmojiLine', () => { + it('should return true for a simple single emoji line', () => { + expect(isSingleEmojiLine('😀')).toBe(true); + }); + + it('should return true for emoji line with whitespace', () => { + expect(isSingleEmojiLine(' 😀 ')).toBe(true); + }); + + it('should return true for emoji line with
tag before', () => { + expect(isSingleEmojiLine('
😀')).toBe(true); + }); + + it('should return true for emoji line with
tag after', () => { + expect(isSingleEmojiLine('😀
')).toBe(true); + }); + + it('should return true for emoji line with
tag', () => { + expect(isSingleEmojiLine('
😀
')).toBe(true); + }); + + it('should return true for emoji line with multiple
tags', () => { + expect(isSingleEmojiLine('

😀

')).toBe(true); + }); + + it('should return true for emoji line with case-insensitive
tags', () => { + expect(isSingleEmojiLine('
😀
')).toBe(true); + }); + + it('should return true for emoji with multiple characters inside', () => { + expect(isSingleEmojiLine('👨‍👩‍👧‍👦')).toBe(true); + }); + + it('should return false for empty string', () => { + expect(isSingleEmojiLine('')).toBe(false); + }); + + it('should return false for line with only
tags', () => { + expect(isSingleEmojiLine('

')).toBe(false); + }); + + it('should return false for text before emoji', () => { + expect(isSingleEmojiLine('hello 😀')).toBe(false); + }); + + it('should return false for text after emoji', () => { + expect(isSingleEmojiLine('😀 world')).toBe(false); + }); + + it('should return false for unclosed emoji tag', () => { + expect(isSingleEmojiLine('😀')).toBe(false); + }); + + it('should return false for plain text without emoji tags', () => { + expect(isSingleEmojiLine('just some text')).toBe(false); + }); + + it('should return false for emoji without tags', () => { + expect(isSingleEmojiLine('😀')).toBe(false); + }); + + it('should return true for emoji line with whitespace and
combined', () => { + expect(isSingleEmojiLine('
😀
')).toBe(true); + }); +}); From f2d09e5f7a3f79670ba906bccce37c95debb13c1 Mon Sep 17 00:00:00 2001 From: paulnjs Date: Mon, 29 Dec 2025 22:54:03 +0700 Subject: [PATCH 13/13] refactor code --- src/components/EmojiWithTooltip/index.ios.tsx | 4 +-- src/components/EmojiWithTooltip/types.ts | 2 +- .../HTMLRenderers/EmojiRenderer.tsx | 2 +- src/libs/EmojiUtils.tsx | 4 +-- .../report/comment/TextCommentFragment.tsx | 4 +-- tests/unit/libs/EmojiUtilsTest.ts | 36 +++++++++---------- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/components/EmojiWithTooltip/index.ios.tsx b/src/components/EmojiWithTooltip/index.ios.tsx index b76cc01c9f75..dc58e010157c 100644 --- a/src/components/EmojiWithTooltip/index.ios.tsx +++ b/src/components/EmojiWithTooltip/index.ios.tsx @@ -3,10 +3,10 @@ import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; import type EmojiWithTooltipProps from './types'; -function EmojiWithTooltip({emojiCode, style = {}, isMedium = false, oneLine = false}: EmojiWithTooltipProps) { +function EmojiWithTooltip({emojiCode, style = {}, isMedium = false, isOnSeparateLine = false}: EmojiWithTooltipProps) { const styles = useThemeStyles(); const isCustomEmoji = emojiCode === '\uE100'; - if (oneLine) { + if (isOnSeparateLine) { return {emojiCode}; } diff --git a/src/components/EmojiWithTooltip/types.ts b/src/components/EmojiWithTooltip/types.ts index 850eff5a5344..31fe4d1ada4c 100644 --- a/src/components/EmojiWithTooltip/types.ts +++ b/src/components/EmojiWithTooltip/types.ts @@ -4,7 +4,7 @@ type EmojiWithTooltipProps = { emojiCode: string; style?: StyleProp; isMedium?: boolean; - oneLine?: boolean; + isOnSeparateLine?: boolean; }; export default EmojiWithTooltipProps; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx index 2d6c08449aaf..13c2d0cafb7d 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx @@ -18,7 +18,7 @@ function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps/gi, '').trim(); return /^.*<\/emoji>$/.test(trimmed); } @@ -694,5 +694,5 @@ export { containsCustomEmoji, containsOnlyCustomEmoji, processFrequentlyUsedEmojis, - isSingleEmojiLine, + isEmojiOnSeparateLine, }; diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index b57a5303a9ef..37d672977772 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -11,7 +11,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import convertToLTR from '@libs/convertToLTR'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; -import {containsOnlyCustomEmoji as containsOnlyCustomEmojiUtil, containsOnlyEmojis as containsOnlyEmojisUtil, isSingleEmojiLine, splitTextWithEmojis} from '@libs/EmojiUtils'; +import {containsOnlyCustomEmoji as containsOnlyCustomEmojiUtil, containsOnlyEmojis as containsOnlyEmojisUtil, isEmojiOnSeparateLine, splitTextWithEmojis} from '@libs/EmojiUtils'; import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; import {getHtmlWithAttachmentID, getTextFromHtml} from '@libs/ReportActionsUtils'; @@ -92,7 +92,7 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM } const lines = htmlContent.split(//i); const processedLines = lines.map((line) => { - if (isSingleEmojiLine(line)) { + if (isEmojiOnSeparateLine(line)) { return line.replace('', ''); } return line.replace('', ''); diff --git a/tests/unit/libs/EmojiUtilsTest.ts b/tests/unit/libs/EmojiUtilsTest.ts index 4f651d9fa1af..e475991a54f0 100644 --- a/tests/unit/libs/EmojiUtilsTest.ts +++ b/tests/unit/libs/EmojiUtilsTest.ts @@ -1,4 +1,4 @@ -import {isSingleEmojiLine, processFrequentlyUsedEmojis} from '@libs/EmojiUtils'; +import {isEmojiOnSeparateLine, processFrequentlyUsedEmojis} from '@libs/EmojiUtils'; import type {FrequentlyUsedEmoji} from '@src/types/onyx'; // Mock the Emojis module @@ -303,68 +303,68 @@ describe('processFrequentlyUsedEmojis', () => { }); }); -describe('isSingleEmojiLine', () => { +describe('isEmojiOnSeparateLine', () => { it('should return true for a simple single emoji line', () => { - expect(isSingleEmojiLine('😀')).toBe(true); + expect(isEmojiOnSeparateLine('😀')).toBe(true); }); it('should return true for emoji line with whitespace', () => { - expect(isSingleEmojiLine(' 😀 ')).toBe(true); + expect(isEmojiOnSeparateLine(' 😀 ')).toBe(true); }); it('should return true for emoji line with
tag before', () => { - expect(isSingleEmojiLine('
😀')).toBe(true); + expect(isEmojiOnSeparateLine('
😀')).toBe(true); }); it('should return true for emoji line with
tag after', () => { - expect(isSingleEmojiLine('😀
')).toBe(true); + expect(isEmojiOnSeparateLine('😀
')).toBe(true); }); it('should return true for emoji line with
tag', () => { - expect(isSingleEmojiLine('
😀
')).toBe(true); + expect(isEmojiOnSeparateLine('
😀
')).toBe(true); }); it('should return true for emoji line with multiple
tags', () => { - expect(isSingleEmojiLine('

😀

')).toBe(true); + expect(isEmojiOnSeparateLine('

😀

')).toBe(true); }); it('should return true for emoji line with case-insensitive
tags', () => { - expect(isSingleEmojiLine('
😀
')).toBe(true); + expect(isEmojiOnSeparateLine('
😀
')).toBe(true); }); it('should return true for emoji with multiple characters inside', () => { - expect(isSingleEmojiLine('👨‍👩‍👧‍👦')).toBe(true); + expect(isEmojiOnSeparateLine('👨‍👩‍👧‍👦')).toBe(true); }); it('should return false for empty string', () => { - expect(isSingleEmojiLine('')).toBe(false); + expect(isEmojiOnSeparateLine('')).toBe(false); }); it('should return false for line with only
tags', () => { - expect(isSingleEmojiLine('

')).toBe(false); + expect(isEmojiOnSeparateLine('

')).toBe(false); }); it('should return false for text before emoji', () => { - expect(isSingleEmojiLine('hello 😀')).toBe(false); + expect(isEmojiOnSeparateLine('hello 😀')).toBe(false); }); it('should return false for text after emoji', () => { - expect(isSingleEmojiLine('😀 world')).toBe(false); + expect(isEmojiOnSeparateLine('😀 world')).toBe(false); }); it('should return false for unclosed emoji tag', () => { - expect(isSingleEmojiLine('😀')).toBe(false); + expect(isEmojiOnSeparateLine('😀')).toBe(false); }); it('should return false for plain text without emoji tags', () => { - expect(isSingleEmojiLine('just some text')).toBe(false); + expect(isEmojiOnSeparateLine('just some text')).toBe(false); }); it('should return false for emoji without tags', () => { - expect(isSingleEmojiLine('😀')).toBe(false); + expect(isEmojiOnSeparateLine('😀')).toBe(false); }); it('should return true for emoji line with whitespace and
combined', () => { - expect(isSingleEmojiLine('
😀
')).toBe(true); + expect(isEmojiOnSeparateLine('
😀
')).toBe(true); }); });