diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index 9662014a..42bde3d2 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -724,3 +724,145 @@ test('Test quotes markdown replacement with heading inside', () => { testString = '> # heading A\n> # heading B'; expect(parser.replace(testString)).toBe('
'); }); + +// Valid text that should match for user mentions +test('Test for user mention with @username@domain.com', () => { + const testString = '@username@expensify.com'; + const resultString = 'heading A
heading B
@username@expensify.com'; + expect(parser.replace(testString)).toBe(resultString); +}); + +test('Test for user mention with inlineCodeBlock style', () => { + const testString = '`@username@expensify.com`'; + const resultString = '
@username@expensify.com
';
+ expect(parser.replace(testString)).toBe(resultString);
+});
+
+test('Test for user mention without space or supported styling character', () => {
+ const testString = 'hi@username@expensify.com';
+ const resultString = 'hi@username@expensify.com';
+ expect(parser.replace(testString)).toBe(resultString);
+});
+
+test('Test for user mention without space or supported styling character', () => {
+ const testString = 'hi@here';
+ const resultString = 'hi@here';
+ expect(parser.replace(testString)).toBe(resultString);
+});
+
+test('Test for @here mention with codefence style', () => {
+ const testString = '```@here```';
+ const resultString = '@here'; + expect(parser.replace(testString)).toBe(resultString); +}); + +test('Test for @here mention with inlineCodeBlock style', () => { + const testString = '`@here`'; + const resultString = '
@here
';
+ expect(parser.replace(testString)).toBe(resultString);
+});
+
+// Examples that should match for here mentions:
+test('Test for here mention with @here', () => {
+ const testString = '@here';
+ const resultString = '$2
$3',
},
+ /**
+ * Apply the hereMention first because the string @here is still a valid mention for the userMention regex.
+ * This ensures that the hereMention is always considered first, even if it is followed by a valid userMention.
+ */
+ {
+ name: 'hereMentions',
+ regex: /((`||)\s*?)?[`.a-zA-Z]?@here\b/gm,
+ replacement: (match) => {
+ if (!Str.isValidMention(match)) {
+ return match;
+ }
+ return `${match} `;
+ },
+ },
+
+ /**
+ * This regex matches a valid user mention in a string.
+ * A user mention is a string that starts with the '@' symbol and is followed by a valid user's primary login
+ *
+ * Note: currently we are only allowing mentions in a format of @+19728974297@expensify.sms and @username@example.com
+ * The username can contain any combination of alphanumeric letters, numbers, and underscores
+ */
+ {
+ name: 'userMentions',
+ regex: new RegExp(`((`||)\\s*?)?[\`.a-zA-Z]?@+${CONST.REG_EXP.EMAIL_PART}`, 'gm'),
+ replacement: (match) => {
+ if (!Str.isValidMention(match)) {
+ return match;
+ }
+ return `${match} `;
+ },
+ },
+
/**
* Converts markdown style links to anchor tags e.g. [Expensify](concierge@expensify.com)
* We need to convert before the auto email link rule and the manual link rule since it will not try to create a link
diff --git a/lib/str.js b/lib/str.js
index 01bd0268..7cab4f9f 100644
--- a/lib/str.js
+++ b/lib/str.js
@@ -935,6 +935,25 @@ const Str = {
return CONST.SMS.E164_REGEX.test(phone);
},
+ /**
+ * We validate mentions by checking if it's first character is an allowed character
+ * and by checking that we make sure it isn't inside other tags where mentions aren't allowed.
+ * For example, *@username@expensify.com* is a valid mention because we allow bold styling for it,
+ * but `@username@expensify.com` is not because we do not allow mentions within code
+ *
+ * @param {String} mention
+ * @returns {bool}
+ */
+ isValidMention(mention) {
+ // A valid mention starts with a space, *, _, #, ', ", or @ (with no preceding characters).
+ const startsWithValidChar = /[\s*~_#'"@]/g.test(mention.charAt(0));
+
+ // We don't support mention inside code or codefence styling,
+ // for example using `@username@expensify.com` or ```@username@expensify.com``` will be invalid.
+ const containsInvalidTag = /(||`)/g.test(mention);
+ return startsWithValidChar && !containsInvalidTag;
+ },
+
/**
* Returns text without our SMS domain
*