diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index 75e7daee6..db8893afc 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -304,15 +304,29 @@ test('Test markdown replacement for emojis with emails', () => { + '[πŸ˜„abc@gmail.com](abc@gmail.com) ' + '[πŸ˜„ abc@gmail.com ](abc@gmail.com) ' const result = 'Do not replace the emoji with link ' - + '[πŸ˜„](abc@gmail.com) ' - + '[πŸ˜„]( abc@gmail.com) ' - + '[πŸ˜„] abc@gmail.com ' - + '[πŸ˜„]((abc@gmail.com)) ' - + '[πŸ˜„abc@gmail.com](abc@gmail.com) ' - + '[πŸ˜„ abc@gmail.com ](abc@gmail.com) ' + + '[πŸ˜„](abc@gmail.com) ' + + '[πŸ˜„]( abc@gmail.com) ' + + '[πŸ˜„] abc@gmail.com ' + + '[πŸ˜„]((abc@gmail.com)) ' + + '[πŸ˜„abc@gmail.com](abc@gmail.com) ' + + '[πŸ˜„ abc@gmail.com ](abc@gmail.com) ' expect(parser.replace(testString)).toBe(result); }); +test('Test markdown replacement for composite emoji', () => { + const testString = 'Replace composite emoji with only one emoji tag ' + + 'πŸ˜Άβ€πŸŒ«οΈ ' + + 'πŸ§‘β€πŸ”§ ' + + 'πŸ‘¨β€πŸ« ' + + 'πŸ‘¨πŸΎβ€β€οΈβ€πŸ‘¨πŸ½ ' + const result = 'Replace composite emoji with only one emoji tag ' + + 'πŸ˜Άβ€πŸŒ«οΈ ' + + 'πŸ§‘β€πŸ”§ ' + + 'πŸ‘¨β€πŸ« ' + + 'πŸ‘¨πŸΎβ€β€οΈβ€πŸ‘¨πŸ½ ' + expect(parser.replace(testString)).toBe(result); +}) + // Markdown style links replaced successfully test('Test markdown style links', () => { @@ -1123,7 +1137,7 @@ test('Test for link with no content', () => { test('Test for link with emoji', () => { const testString = '[πŸ˜€](www.link.com)'; - const resultString = '[πŸ˜€](www.link.com)'; + const resultString = '[πŸ˜€](www.link.com)';; expect(parser.replace(testString)).toBe(resultString); }); test('Test quotes markdown replacement with heading inside', () => { diff --git a/lib/CONST.d.ts b/lib/CONST.d.ts index aea62aae2..02eb3fe2c 100644 --- a/lib/CONST.d.ts +++ b/lib/CONST.d.ts @@ -289,6 +289,12 @@ export declare const CONST: { * Regex matching an text containing an Emoji */ readonly EMOJIS: RegExp; + /** + * Regex matching an text containing an Emoji that can be a single emoji or made up by some different emojis + * + * @type RegExp + */ + readonly EMOJI_RULE: RegExp; }; readonly REPORT: { /** diff --git a/lib/CONST.jsx b/lib/CONST.jsx index b40225615..abdc31b3a 100644 --- a/lib/CONST.jsx +++ b/lib/CONST.jsx @@ -357,6 +357,12 @@ export const CONST = { * @type RegExp */ EMOJIS: /[\p{Extended_Pictographic}\u200d\u{1f1e6}-\u{1f1ff}\u{1f3fb}-\u{1f3ff}\u{e0020}-\u{e007f}\u20E3\uFE0F]|[#*0-9]\uFE0F?\u20E3/gu, + /** + * Regex matching an text containing an Emoji that can be a single emoji or made up by some different emojis + * + * @type RegExp + */ + EMOJI_RULE: /[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/gu, }, REPORT: { diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 690e8ac4a..e3822d20d 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -18,8 +18,15 @@ export default class ExpensiMark { * @type {Object[]} */ this.rules = [ + // Apply the emoji first avoid applying any other formatting rules inside of it + { + name: 'emoji', + regex: CONST.REG_EXP.EMOJI_RULE, + replacement: match => `${match}` + }, + /** - * Apply the code-fence first so that we avoid replacing anything inside of it that we're not supposed to + * Apply the code-fence to avoid replacing anything inside of it that we're not supposed to * (aka any rule with the '(?![^<]*<\/pre>)' avoidance in it */ { diff --git a/package-lock.json b/package-lock.json index 074b0b93f..ae5d8679c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "underscore": "1.13.6" }, "devDependencies": { - "@babel/preset-env": "^7.23.9", + "@babel/preset-env": "^7.24.0", "babel-jest": "^29.0.0", "babelify": "10.0.0", "eslint-config-expensify": "^2.0.16", @@ -353,9 +353,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1320,16 +1320,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", + "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" + "@babel/plugin-transform-parameters": "^7.24.1" }, "engines": { "node": ">=6.9.0" @@ -1388,12 +1387,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", + "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1622,14 +1621,14 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", - "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", + "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", @@ -1682,7 +1681,7 @@ "@babel/plugin-transform-new-target": "^7.23.3", "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.24.0", "@babel/plugin-transform-object-super": "^7.23.3", "@babel/plugin-transform-optional-catch-binding": "^7.23.4", "@babel/plugin-transform-optional-chaining": "^7.23.4", diff --git a/package.json b/package.json index 61be7b9a7..cfa587744 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "underscore": "1.13.6" }, "devDependencies": { - "@babel/preset-env": "^7.23.9", + "@babel/preset-env": "^7.24.0", "babel-jest": "^29.0.0", "babelify": "10.0.0", "eslint-config-expensify": "^2.0.16",