From 8b1666e4a447795ff448d2e2deb61a1629fd5e4f Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 00:10:07 +0200 Subject: [PATCH 1/5] add room mentions regex --- __tests__/ExpensiMark-HTML-test.js | 89 +++++++++++++++++++++++++++++- lib/ExpensiMark.js | 7 +++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index db8893af..e2bf1112 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -1959,5 +1959,92 @@ describe('Image markdown conversion to html tag', () => { const testString = '![`code`](https://example.com/image.png)'; const resultString = '<code>code</code>'; expect(parser.replace(testString)).toBe(resultString); - }) + }); +}); + +describe('room mentions', () => { + test('simple room mention', () => { + const testString = '#room'; + const resultString = '#room'; + expect(parser.replace(testString)).toBe(resultString); + }); + + test('room mention with leading word and space', () => { + const testString = 'hi all #room'; + const resultString = 'hi all #room'; + 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 = '

room

'; + expect(parser.replace(testString)).toBe(resultString); + }); + + test.only('room mention with markdown syntax before the # prefix', () => { + const testString = 'hello *#room'; + const resultString = 'hello *#room'; + 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 = '#room' + + ' #room' + + ' #room' + + ' #room' + + ' #room' + + ' #room abc' + + ' #room*' + + ' #room~' + + ' #room#' + + ' #room@' + + ' #room$' + + ' #room^' + + ' #room(' + + ' #room.' + + ' #room!' + + ' #room?'; + expect(parser.replace(testString)).toBe(resultString); + }); + + test('room mention inside link should not be rendered', () => { + const testString = '[#room](google.com)'; + const resultString = '#room'; + 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 = '#room'; + expect(parser.replace(testString)).toBe(resultString); + }); + + test('room mention inside code block should not be rendered', () => { + const testString = '```\n#room\n```'; + const resultString = '
#room
'; + expect(parser.replace(testString)).toBe(resultString); + }); }); diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index e3822d20..95c28c68 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -169,6 +169,13 @@ export default class ExpensiMark { }, }, + { + name: 'roomMentions', + + regex: /(?$1', + }, + /** * 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 From 9b9a1afaa29b3c1fe13651094cb815b2dc7557f6 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 00:26:36 +0200 Subject: [PATCH 2/5] pdate mention-room to mention-report per design doc --- __tests__/ExpensiMark-HTML-test.js | 102 ++++++++++++------------- __tests__/ExpensiMark-Markdown-test.js | 18 ++--- lib/ExpensiMark.js | 2 +- 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index e2bf1112..4d2c038b 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -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 ' + '[πŸ˜„](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); }); @@ -318,14 +318,14 @@ test('Test markdown replacement for composite emoji', () => { + 'πŸ˜Άβ€πŸŒ«οΈ ' + 'πŸ§‘β€πŸ”§ ' + 'πŸ‘¨β€πŸ« ' - + 'πŸ‘¨πŸΎβ€β€οΈβ€πŸ‘¨πŸ½ ' + + 'πŸ‘¨πŸΎβ€β€οΈβ€πŸ‘¨πŸ½ '; const result = 'Replace composite emoji with only one emoji tag ' + 'πŸ˜Άβ€πŸŒ«οΈ ' + 'πŸ§‘β€πŸ”§ ' + 'πŸ‘¨β€πŸ« ' - + 'πŸ‘¨πŸΎβ€β€οΈβ€πŸ‘¨πŸ½ ' + + 'πŸ‘¨πŸΎβ€β€οΈβ€πŸ‘¨πŸ½ '; expect(parser.replace(testString)).toBe(result); -}) +}); // Markdown style links replaced successfully @@ -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 = '"om https://www.she.com/"
' + - '"om https://www.she.com"
' + - '"https://www.she.com/ end"
' + - '"https://www.she.com end"
' + - 'https://www.she.com/path?test="123"
' + - 'https://www.she.com/path?test="123/"
' + - '"https://www.she.com/path?test="123"
' + - '"https://www.she.com/path?test="123/"
' + - 'https://www.she.com/path?test=123"
' + - 'https://www.she.com/path?test=123/"
' + - 'https://www.she.com/path?test=/"123"
' + - 'https://www.she.com/path?test=/"123/"'; + 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 = '"om https://www.she.com/"
' + + '"om https://www.she.com"
' + + '"https://www.she.com/ end"
' + + '"https://www.she.com end"
' + + 'https://www.she.com/path?test="123"
' + + 'https://www.she.com/path?test="123/"
' + + '"https://www.she.com/path?test="123"
' + + '"https://www.she.com/path?test="123/"
' + + 'https://www.she.com/path?test=123"
' + + 'https://www.she.com/path?test=123/"
' + + 'https://www.she.com/path?test=/"123"
' + + 'https://www.she.com/path?test=/"123/"'; expect(parser.replace(urlTestStartString)).toBe(urlTestReplacedString); }); @@ -732,7 +732,7 @@ test('Test urls with unmatched closing parentheses autolinks correctly', () => { resultString: 'google.com/(toto))titi)', }, ]; - testCases.forEach(testCase => { + testCases.forEach((testCase) => { expect(parser.replace(testCase.testString)).toBe(testCase.resultString); }); }); @@ -867,7 +867,7 @@ test('Test urls autolinks correctly', () => { }, ]; - testCases.forEach(testCase => { + testCases.forEach((testCase) => { expect(parser.replace(testCase.testString)).toBe(testCase.resultString); }); }); @@ -1137,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', () => { @@ -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('*test*'); @@ -1965,13 +1965,13 @@ describe('Image markdown conversion to html tag', () => { describe('room mentions', () => { test('simple room mention', () => { const testString = '#room'; - const resultString = '#room'; + const resultString = '#room'; expect(parser.replace(testString)).toBe(resultString); }); test('room mention with leading word and space', () => { const testString = 'hi all #room'; - const resultString = 'hi all #room'; + const resultString = 'hi all #room'; expect(parser.replace(testString)).toBe(resultString); }); @@ -1989,7 +1989,7 @@ describe('room mentions', () => { test.only('room mention with markdown syntax before the # prefix', () => { const testString = 'hello *#room'; - const resultString = 'hello *#room'; + const resultString = 'hello *#room'; expect(parser.replace(testString)).toBe(resultString); }); @@ -2011,22 +2011,22 @@ describe('room mentions', () => { + ' #room!' + ' #room?'; - const resultString = '#room' - + ' #room' - + ' #room' - + ' #room' + const resultString = '#room' + + ' #room' + + ' #room' + + ' #room' + ' #room' - + ' #room abc' - + ' #room*' - + ' #room~' - + ' #room#' - + ' #room@' - + ' #room$' - + ' #room^' - + ' #room(' - + ' #room.' - + ' #room!' - + ' #room?'; + + ' #room abc' + + ' #room*' + + ' #room~' + + ' #room#' + + ' #room@' + + ' #room$' + + ' #room^' + + ' #room(' + + ' #room.' + + ' #room!' + + ' #room?'; expect(parser.replace(testString)).toBe(resultString); }); diff --git a/__tests__/ExpensiMark-Markdown-test.js b/__tests__/ExpensiMark-Markdown-test.js index ada9fd90..e8e27d90 100644 --- a/__tests__/ExpensiMark-Markdown-test.js +++ b/__tests__/ExpensiMark-Markdown-test.js @@ -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
'; + const resultString = '#This is not a heading1 because starts with a # but has no space after it
'; expect(parser.replace(testString)).toBe(resultString); }); @@ -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 = '

test heading

Code snippet\n

link

'; - 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', () => { @@ -758,17 +758,17 @@ test('Mention html to markdown', () => { }); describe('Image tag conversion to markdown', () => { - test('Image with alt attribute', () => { - const testString = 'image'; - const resultString = '![image](https://example.com/image.png)'; - expect(parser.htmlToMarkdown(testString)).toBe(resultString); - }); + test('Image with alt attribute', () => { + const testString = '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 = ''; 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 = '*bold* _italic_ ~strike~'; diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 95c28c68..e42ac07f 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -173,7 +173,7 @@ export default class ExpensiMark { name: 'roomMentions', regex: /(?$1', + replacement: '$1', }, /** From b47e67748cb7f945e6dcc22367c3bb3a07b4052a Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 11:18:36 +0200 Subject: [PATCH 3/5] add additional room mentions tests --- __tests__/ExpensiMark-HTML-test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index 4d2c038b..f8413076 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -2047,4 +2047,22 @@ describe('room mentions', () => { const resultString = '
#room
'; 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 = '#room123'; + expect(parser.replace(testString)).toBe(resultString); + }); + + test('room mention with hyphens', () => { + const testString = '#room-name'; + const resultString = '#room-name'; + expect(parser.replace(testString)).toBe(resultString); + }); }); From 003c781b3cae89dd6422e5a5f212adee7d26d6b8 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 11:18:57 +0200 Subject: [PATCH 4/5] add comment for room mention regex --- lib/ExpensiMark.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index e42ac07f..7d5be4e2 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -169,6 +169,12 @@ 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 + */ { name: 'roomMentions', From 278378bc4e6742a62002e6d82c97d1ab7b89de7d Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 10 Apr 2024 12:14:33 +0200 Subject: [PATCH 5/5] remove unwanted test.only & add additional tests for room names with hyphens and numbers --- __tests__/ExpensiMark-HTML-test.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index f8413076..519e39e5 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -1987,7 +1987,7 @@ describe('room mentions', () => { expect(parser.replace(testString)).toBe(resultString); }); - test.only('room mention with markdown syntax before the # prefix', () => { + test('room mention with markdown syntax before the # prefix', () => { const testString = 'hello *#room'; const resultString = 'hello *#room'; expect(parser.replace(testString)).toBe(resultString); @@ -2056,7 +2056,7 @@ describe('room mentions', () => { test('room mention with numbers', () => { const testString = '#room123'; - const resultString = '#room123'; + const resultString = '#room123'; expect(parser.replace(testString)).toBe(resultString); }); @@ -2065,4 +2065,10 @@ describe('room mentions', () => { const resultString = '#room-name'; expect(parser.replace(testString)).toBe(resultString); }); + + test('room mention with hypens and numbers', () => { + const testString = '#room-name123'; + const resultString = '#room-name123'; + expect(parser.replace(testString)).toBe(resultString); + }); });