Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 145 additions & 34 deletions __tests__/ExpensiMark-HTML-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,14 +302,14 @@ test('Test markdown replacement for emojis with emails', () => {
+ '[😄] abc@gmail.com '
+ '[😄]((abc@gmail.com)) '
+ '[😄abc@gmail.com](abc@gmail.com) '
+ '[😄 abc@gmail.com ](abc@gmail.com) '
+ '[😄 abc@gmail.com ](abc@gmail.com) ';
const result = 'Do not replace the emoji with link '
+ '[<emoji>😄</emoji>](<a href="mailto:abc@gmail.com">abc@gmail.com</a>) '
+ '[<emoji>😄</emoji>]( <a href="mailto:abc@gmail.com">abc@gmail.com</a>) '
+ '[<emoji>😄</emoji>] <a href="mailto:abc@gmail.com">abc@gmail.com</a> '
+ '[<emoji>😄</emoji>]((<a href="mailto:abc@gmail.com">abc@gmail.com</a>)) '
+ '[<emoji>😄</emoji><a href="mailto:abc@gmail.com">abc@gmail.com</a>](<a href="mailto:abc@gmail.com">abc@gmail.com</a>) '
+ '[<emoji>😄</emoji> <a href="mailto:abc@gmail.com">abc@gmail.com</a> ](<a href="mailto:abc@gmail.com">abc@gmail.com</a>) '
+ '[<emoji>😄</emoji> <a href="mailto:abc@gmail.com">abc@gmail.com</a> ](<a href="mailto:abc@gmail.com">abc@gmail.com</a>) ';
expect(parser.replace(testString)).toBe(result);
});

Expand All @@ -318,14 +318,14 @@ test('Test markdown replacement for composite emoji', () => {
+ '😶‍🌫️ '
+ '🧑‍🔧 '
+ '👨‍🏫 '
+ '👨🏾‍❤️‍👨🏽 '
+ '👨🏾‍❤️‍👨🏽 ';
const result = 'Replace composite emoji with only one emoji tag '
+ '<emoji>😶‍🌫️</emoji> '
+ '<emoji>🧑‍🔧</emoji> '
+ '<emoji>👨‍🏫</emoji> '
+ '<emoji>👨🏾‍❤️‍👨🏽</emoji> '
+ '<emoji>👨🏾‍❤️‍👨🏽</emoji> ';
expect(parser.replace(testString)).toBe(result);
})
});


// Markdown style links replaced successfully
Expand Down Expand Up @@ -550,30 +550,30 @@ test('Test wrapped URLs', () => {
});

test('Test Url, where double quote is not allowed', () => {
const urlTestStartString = '"om https://www.she.com/"\n' +
'"om https://www.she.com"\n' +
'"https://www.she.com/ end"\n' +
'"https://www.she.com end"\n' +
'https://www.she.com/path?test="123"\n' +
'https://www.she.com/path?test="123/"\n' +
'"https://www.she.com/path?test="123"\n' +
'"https://www.she.com/path?test="123/"\n' +
'https://www.she.com/path?test=123"\n' +
'https://www.she.com/path?test=123/"\n' +
'https://www.she.com/path?test=/"123"\n' +
'https://www.she.com/path?test=/"123/"';
const urlTestReplacedString = '&quot;om <a href=\"https://www.she.com/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/</a>&quot;<br />' +
'&quot;om <a href=\"https://www.she.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com</a>&quot;<br />' +
'&quot;<a href=\"https://www.she.com/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/</a> end&quot;<br />' +
'&quot;<a href=\"https://www.she.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com</a> end&quot;<br />' +
'<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123&quot;<br />' +
'<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123/&quot;<br />' +
'&quot;<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123&quot;<br />' +
'&quot;<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123/&quot;<br />' +
'<a href=\"https://www.she.com/path?test=123\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=123</a>&quot;<br />' +
'<a href=\"https://www.she.com/path?test=123/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=123/</a>&quot;<br />' +
'<a href=\"https://www.she.com/path?test=/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=/</a>&quot;123&quot;<br />' +
'<a href=\"https://www.she.com/path?test=/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=/</a>&quot;123/&quot;';
const urlTestStartString = '"om https://www.she.com/"\n'
+ '"om https://www.she.com"\n'
+ '"https://www.she.com/ end"\n'
+ '"https://www.she.com end"\n'
+ 'https://www.she.com/path?test="123"\n'
+ 'https://www.she.com/path?test="123/"\n'
+ '"https://www.she.com/path?test="123"\n'
+ '"https://www.she.com/path?test="123/"\n'
+ 'https://www.she.com/path?test=123"\n'
+ 'https://www.she.com/path?test=123/"\n'
+ 'https://www.she.com/path?test=/"123"\n'
+ 'https://www.she.com/path?test=/"123/"';
const urlTestReplacedString = '&quot;om <a href=\"https://www.she.com/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/</a>&quot;<br />'
+ '&quot;om <a href=\"https://www.she.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com</a>&quot;<br />'
+ '&quot;<a href=\"https://www.she.com/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/</a> end&quot;<br />'
+ '&quot;<a href=\"https://www.she.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com</a> end&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123/&quot;<br />'
+ '&quot;<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123&quot;<br />'
+ '&quot;<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123/&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=123\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=123</a>&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=123/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=123/</a>&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=/</a>&quot;123&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=/</a>&quot;123/&quot;';
expect(parser.replace(urlTestStartString)).toBe(urlTestReplacedString);
});

Expand Down Expand Up @@ -732,7 +732,7 @@ test('Test urls with unmatched closing parentheses autolinks correctly', () => {
resultString: '<a href="https://google.com/(toto)" target="_blank" rel="noreferrer noopener">google.com/(toto)</a>)titi)',
},
];
testCases.forEach(testCase => {
testCases.forEach((testCase) => {
expect(parser.replace(testCase.testString)).toBe(testCase.resultString);
});
});
Expand Down Expand Up @@ -867,7 +867,7 @@ test('Test urls autolinks correctly', () => {
},
];

testCases.forEach(testCase => {
testCases.forEach((testCase) => {
expect(parser.replace(testCase.testString)).toBe(testCase.resultString);
});
});
Expand Down Expand Up @@ -1137,7 +1137,7 @@ test('Test for link with no content', () => {

test('Test for link with emoji', () => {
const testString = '[😀](www.link.com)';
const resultString = '[<emoji>😀</emoji>](<a href="https://www.link.com" target="_blank" rel="noreferrer noopener">www.link.com</a>)';;
const resultString = '[<emoji>😀</emoji>](<a href="https://www.link.com" target="_blank" rel="noreferrer noopener">www.link.com</a>)';
expect(parser.replace(testString)).toBe(resultString);
});
test('Test quotes markdown replacement with heading inside', () => {
Expand Down Expand Up @@ -1472,7 +1472,7 @@ test('Test for mention inside link and email markdown', () => {
expect(parser.replace(testString)).toBe(resultString);
});

test('Skip rendering invalid markdown',() => {
test('Skip rendering invalid markdown', () => {
let testString = '_*test_*';
expect(parser.replace(testString)).toBe('<em>*test</em>*');

Expand Down Expand Up @@ -1959,5 +1959,116 @@ describe('Image markdown conversion to html tag', () => {
const testString = '![`code`](https://example.com/image.png)';
const resultString = '<img src="https://example.com/image.png" alt="<code>code</code>" />';
expect(parser.replace(testString)).toBe(resultString);
})
});
});

describe('room mentions', () => {
Comment thread
robertKozik marked this conversation as resolved.
test('simple room mention', () => {
const testString = '#room';
const resultString = '<mention-report>#room</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with leading word and space', () => {
const testString = 'hi all #room';
const resultString = 'hi all <mention-report>#room</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with leading word and no space', () => {
const testString = 'hi all#room';
const resultString = 'hi all#room';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with space between hash and room name', () => {
const testString = '# room';
const resultString = '<h1>room</h1>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with markdown syntax before the # prefix', () => {
const testString = 'hello *#room';
const resultString = 'hello *<mention-report>#room</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with italic, bold and strikethrough styles', () => {
const testString = '#room'
+ ' _#room_'
+ ' *#room*'
+ ' ~#room~'
+ ' [#room](google.com)'
+ ' #room abc'
+ ' #room*'
+ ' #room~'
+ ' #room#'
+ ' #room@'
+ ' #room$'
+ ' #room^'
+ ' #room('
+ ' #room.'
+ ' #room!'
+ ' #room?';

const resultString = '<mention-report>#room</mention-report>'
+ ' <em><mention-report>#room</mention-report></em>'
+ ' <strong><mention-report>#room</mention-report></strong>'
+ ' <del><mention-report>#room</mention-report></del>'
+ ' <a href="https://google.com" target="_blank" rel="noreferrer noopener">#room</a>'
+ ' <mention-report>#room</mention-report> abc'
+ ' <mention-report>#room</mention-report>*'
+ ' <mention-report>#room</mention-report>~'
+ ' <mention-report>#room</mention-report>#'
+ ' <mention-report>#room</mention-report>@'
+ ' <mention-report>#room</mention-report>$'
+ ' <mention-report>#room</mention-report>^'
+ ' <mention-report>#room</mention-report>('
+ ' <mention-report>#room</mention-report>.'
+ ' <mention-report>#room</mention-report>!'
+ ' <mention-report>#room</mention-report>?';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention inside link should not be rendered', () => {
const testString = '[#room](google.com)';
const resultString = '<a href="https://google.com" target="_blank" rel="noreferrer noopener">#room</a>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with space inside link should not be rendered', () => {
const testString = '[ #room](google.com/sub#111)';
const resultString = '<a href="https://google.com/sub#111" target="_blank" rel="noreferrer noopener">#room</a>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention inside code block should not be rendered', () => {
const testString = '```\n#room\n```';
const resultString = '<pre>#room<br /></pre>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention without room name', () => {
const testString = '#';
const resultString = '#';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with numbers', () => {
const testString = '#room123';
const resultString = '<mention-report>#room123</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with hyphens', () => {
const testString = '#room-name';
const resultString = '<mention-report>#room-name</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with hypens and numbers', () => {
const testString = '#room-name123';
const resultString = '<mention-report>#room-name123</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});
});
18 changes: 9 additions & 9 deletions __tests__/ExpensiMark-Markdown-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ test('Test heading1 markdown replacement when # is followed by multiple spaces',

test('Test heading1 markdown when # is not followed by a space', () => {
const testString = '#This is not a heading1 because starts with a # but has no space after it\n';
const resultString = '#This is not a heading1 because starts with a # but has no space after it<br />';
const resultString = '<mention-report>#This</mention-report> is not a heading1 because starts with a # but has no space after it<br />';
expect(parser.replace(testString)).toBe(resultString);
});

Expand Down Expand Up @@ -688,7 +688,7 @@ test('Test codeFence copy from selection does not add extra new line', () => {
expect(parser.htmlToMarkdown(testString)).toBe('```\ncode\n```\ntext');

testString = '<h3>test heading</h3><div><pre class=\"notranslate\"><code class=\"notranslate\">Code snippet\n</code></pre></div><blockquote><p><a href=\"https://www.example.com\">link</a></p></blockquote>';
expect(parser.htmlToMarkdown(testString)).toBe('test heading\n```\nCode snippet\n```\n> [link](https://www.example.com)')
expect(parser.htmlToMarkdown(testString)).toBe('test heading\n```\nCode snippet\n```\n> [link](https://www.example.com)');
});

test('Linebreak should be remained for text between code block', () => {
Expand Down Expand Up @@ -758,17 +758,17 @@ test('Mention html to markdown', () => {
});

describe('Image tag conversion to markdown', () => {
test('Image with alt attribute', () => {
const testString = '<img src="https://example.com/image.png" alt="image" />';
const resultString = '![image](https://example.com/image.png)';
expect(parser.htmlToMarkdown(testString)).toBe(resultString);
});
test('Image with alt attribute', () => {
const testString = '<img src="https://example.com/image.png" alt="image" />';
const resultString = '![image](https://example.com/image.png)';
expect(parser.htmlToMarkdown(testString)).toBe(resultString);
});

test('Image without alt attribute', () => {
test('Image without alt attribute', () => {
const testString = '<img src="https://example.com/image.png" />';
const resultString = '![https://example.com/image.png](https://example.com/image.png)';
expect(parser.htmlToMarkdown(testString)).toBe(resultString);
});
});

test('Image with alt text containing escaped markdown', () => {
const testString = '<img src="https://example.com/image.png" alt="&ast;bold&ast; &lowbar;italic&lowbar; &#126;strike&#126;" />';
Expand Down
13 changes: 13 additions & 0 deletions lib/ExpensiMark.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,19 @@ export default class ExpensiMark {
},
},

/**
* A room mention is a string that starts with the '#' symbol and is followed by a valid room name.
*
* Note: We are allowing mentions in a format of #room-name The room name can contain any
* combination of letters and hyphens
*/
{
Comment thread
robertKozik marked this conversation as resolved.
name: 'roomMentions',

regex: /(?<![^ \n*~_])(#[\p{Ll}0-9-]{1,80})/gmiu,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB but certain improvements were introduced as mentioned in this issue with respect to identifying room mentions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In response to the issue Expensify/App#57195, we added in PR https://github.com/Expensify/expensify-common/pull/840/files the identification of room mentions with : before them.

replacement: '<mention-report>$1</mention-report>',
},

/**
* 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
Expand Down