zrequire('hash_util'); set_global('katex', zrequire('katex', 'katex/dist/katex.min.js')); set_global('marked', zrequire('marked', 'third/marked/lib/marked')); set_global('i18n', global.stub_i18n); zrequire('util'); zrequire('fenced_code'); zrequire('stream_data'); zrequire('people'); zrequire('user_groups'); const emoji_codes = zrequire('emoji_codes', 'generated/emoji/emoji_codes.json'); zrequire('emoji'); zrequire('message_store'); zrequire('markdown'); set_global('location', { origin: 'http://zulip.zulipdev.com', }); set_global('page_params', { realm_users: [], realm_emoji: { 1: {id: 1, name: 'burrito', source_url: '/static/generated/emoji/images/emoji/burrito.png', deactivated: false, }, }, realm_filters: [ [ "#(?P[0-9]{2,8})", "https://trac.zulip.net/ticket/%(id)s", ], [ "ZBUG_(?P[0-9]{2,8})", "https://trac2.zulip.net/ticket/%(id)s", ], [ "ZGROUP_(?P[0-9]{2,8}):(?P[0-9]{1,8})", "https://zone_%(zone)s.zulip.net/ticket/%(id)s", ], ], translate_emoticons: false, }); set_global('blueslip', global.make_zblueslip()); set_global('Image', function () { return {}; }); emoji.initialize(); const doc = ""; set_global('document', doc); set_global('$', global.make_zjquery()); set_global('feature_flags', {local_echo: true}); const people = global.people; const cordelia = { full_name: 'Cordelia Lear', user_id: 101, email: 'cordelia@zulip.com', }; people.add(cordelia); people.add({ full_name: 'Leo', user_id: 102, email: 'leo@zulip.com', }); people.add({ full_name: 'Bobby

Tables

', user_id: 103, email: 'bobby@zulip.com', }); people.add({ full_name: 'Mark Twin', user_id: 104, email: 'twin1@zulip.com', }); people.add({ full_name: 'Mark Twin', user_id: 105, email: 'twin2@zulip.com', }); people.add({ full_name: 'Brother of Bobby|123', user_id: 106, email: 'bobby2@zulip.com', }); people.initialize_current_user(cordelia.user_id); const hamletcharacters = { name: "hamletcharacters", id: 1, description: "Characters of Hamlet", members: [cordelia.user_id], }; const backend = { name: "Backend", id: 2, description: "Backend team", members: [], }; const edgecase_group = { name: "Bobby

Tables

", id: 3, description: "HTML Syntax to check for Markdown edge cases.", members: [], }; global.user_groups.add(hamletcharacters); global.user_groups.add(backend); global.user_groups.add(edgecase_group); const stream_data = global.stream_data; const denmark = { subscribed: false, color: 'blue', name: 'Denmark', stream_id: 1, is_muted: true, }; const social = { subscribed: true, color: 'red', name: 'social', stream_id: 2, is_muted: false, invite_only: true, }; const edgecase_stream = { subscribed: true, color: 'green', name: 'Bobby

Tables

', stream_id: 3, is_muted: false, }; const edgecase_stream_2 = { subscribed: true, color: 'yellow', name: 'Bobby { const input = '\n```\nfenced code\n```\n\nand then after\n'; const expected = '\n\n
fenced code\n
\n\n\n\nand then after\n\n'; const output = fenced_code.process_fenced_code(input); assert.equal(output, expected); }); markdown.initialize(); const bugdown_data = global.read_fixture_data('markdown_test_cases.json'); run_test('bugdown_detection', () => { const no_markup = [ "This is a plaintext message", "This is a plaintext: message", "This is a :plaintext message", "This is a :plaintext message: message", "Contains a not an image.jpeg/ok file", "Contains a not an http://www.google.com/ok/image.png/stop file", "No png to be found here, a png", "No user mention **leo**", "No user mention @what there", "No group mention *hamletcharacters*", "We like to code\n~~~\ndef code():\n we = \"like to do\"\n~~~", "This is a\nmultiline :emoji: here\n message", "This is an :emoji: message", "User Mention @**leo**", "User Mention @**leo f**", "User Mention @**leo with some name**", "Group Mention @*hamletcharacters*", "Stream #**Verona**", "This contains !gravatar(leo@zulip.com)", "And an avatar !avatar(leo@zulip.com) is here", ]; const markup = [ "Contains a https://zulip.com/image.png file", "Contains a https://zulip.com/image.jpg file", "https://zulip.com/image.jpg", "also https://zulip.com/image.jpg", "https://zulip.com/image.jpg too", "Contains a zulip.com/foo.jpeg file", "Contains a https://zulip.com/image.png file", "twitter url https://twitter.com/jacobian/status/407886996565016579", "https://twitter.com/jacobian/status/407886996565016579", "then https://twitter.com/jacobian/status/407886996565016579", "twitter url http://twitter.com/jacobian/status/407886996565016579", "youtube url https://www.youtube.com/watch?v=HHZ8iqswiCw&feature=youtu.be&a", ]; no_markup.forEach(function (content) { assert.equal(markdown.contains_backend_only_syntax(content), false); }); markup.forEach(function (content) { assert.equal(markdown.contains_backend_only_syntax(content), true); }); }); run_test('marked_shared', () => { const tests = bugdown_data.regular_tests; tests.forEach(function (test) { // Ignore tests if specified if (test.ignore === true) { return; } const message = {raw_content: test.input}; page_params.translate_emoticons = test.translate_emoticons || false; markdown.apply_markdown(message); const output = message.content; const error_message = `Failure in test: ${test.name}`; if (test.marked_expected_output) { global.bugdown_assert.notEqual(test.expected_output, output, error_message); global.bugdown_assert.equal(test.marked_expected_output, output, error_message); } else if (test.backend_only_rendering) { assert.equal(markdown.contains_backend_only_syntax(test.input), true); } else { global.bugdown_assert.equal(test.expected_output, output, error_message); } }); }); run_test('message_flags', () => { let message = {raw_content: '@**Leo**'}; markdown.apply_markdown(message); assert(!message.mentioned); assert(!message.mentioned_me_directly); message = {raw_content: '@**Cordelia Lear**'}; markdown.apply_markdown(message); assert(message.mentioned); assert(message.mentioned_me_directly); message = {raw_content: '@**all**'}; markdown.apply_markdown(message); assert(message.mentioned); assert(!message.mentioned_me_directly); }); run_test('marked', () => { const test_cases = [ {input: 'hello', expected: '

hello

'}, {input: 'hello there', expected: '

hello there

'}, {input: 'hello **bold** for you', expected: '

hello bold for you

'}, {input: 'hello ***foo*** for you', expected: '

hello foo for you

'}, {input: '__hello__', expected: '

__hello__

'}, {input: '\n```\nfenced code\n```\n\nand then after\n', expected: '
fenced code\n
\n\n\n

and then after

'}, {input: '\n```\n fenced code trailing whitespace \n```\n\nand then after\n', expected: '
    fenced code trailing whitespace\n
\n\n\n

and then after

'}, {input: '* a\n* list \n* here', expected: '
    \n
  • a
  • \n
  • list
  • \n
  • here
  • \n
'}, {input: '\n```c#\nfenced code special\n```\n\nand then after\n', expected: '
fenced code special\n
\n\n\n

and then after

'}, {input: '\n```vb.net\nfenced code dot\n```\n\nand then after\n', expected: '
fenced code dot\n
\n\n\n

and then after

'}, {input: 'Some text first\n* a\n* list \n* here\n\nand then after', expected: '

Some text first

\n
    \n
  • a
  • \n
  • list
  • \n
  • here
  • \n
\n

and then after

'}, {input: '1. an\n2. ordered \n3. list', expected: '
    \n
  1. an
  2. \n
  3. ordered
  4. \n
  5. list
  6. \n
'}, {input: '\n~~~quote\nquote this for me\n~~~\nthanks\n', expected: '
\n

quote this for me

\n
\n

thanks

'}, {input: 'This is a @**Cordelia Lear** mention', expected: '

This is a @Cordelia Lear mention

'}, {input: 'These @ @**** are not mentions', expected: '

These @ @** are not mentions

'}, {input: 'These # #**** are not mentions', expected: '

These # #** are not mentions

'}, {input: 'These @* are not mentions', expected: '

These @* are not mentions

'}, {input: 'These #* #*** are also not mentions', expected: '

These #* #*** are also not mentions

'}, {input: 'This is a #**Denmark** stream link', expected: '

This is a #Denmark stream link

'}, {input: 'This is #**Denmark** and #**social** stream links', expected: '

This is #Denmark and #social stream links

'}, {input: 'And this is a #**wrong** stream link', expected: '

And this is a #**wrong** stream link

'}, {input: 'This is a #**Denmark>some topic** stream_topic link', expected: '

This is a #Denmark > some topic stream_topic link

'}, {input: 'This has two links: #**Denmark>some topic** and #**social>other topic**.', expected: '

This has two links: #Denmark > some topic and #social > other topic.

'}, {input: 'This is not a #**Denmark>** stream_topic link', expected: '

This is not a #**Denmark>** stream_topic link

'}, {input: 'mmm...:burrito:s', expected: '

mmm...:burrito:s

'}, {input: 'This is an :poop: message', expected: '

This is an :poop: message

'}, {input: "\ud83d\udca9", expected: '

:poop:

'}, {input: '\u{1f6b2}', expected: '

\u{1f6b2}

' }, {input: 'Silent mention: @_**Cordelia Lear**', expected: '

Silent mention: Cordelia Lear

'}, {input: '> Mention in quote: @**Cordelia Lear**\n\nMention outside quote: @**Cordelia Lear**', expected: '
\n

Mention in quote: Cordelia Lear

\n
\n

Mention outside quote: @Cordelia Lear

'}, // Test only those realm filters which don't return True for // `contains_backend_only_syntax()`. Those which return True // are tested separately. {input: 'This is a realm filter #1234 with text after it', expected: '

This is a realm filter #1234 with text after it

'}, {input: '#1234is not a realm filter.', expected: '

#1234is not a realm filter.

'}, {input: 'A pattern written as #1234is not a realm filter.', expected: '

A pattern written as #1234is not a realm filter.

'}, {input: 'This is a realm filter with ZGROUP_123:45 groups', expected: '

This is a realm filter with ZGROUP_123:45 groups

'}, {input: 'This is an !avatar(cordelia@zulip.com) of Cordelia Lear', expected: '

This is an cordelia@zulip.com of Cordelia Lear

'}, {input: 'This is a !gravatar(cordelia@zulip.com) of Cordelia Lear', expected: '

This is a cordelia@zulip.com of Cordelia Lear

'}, {input: 'Test *italic*', expected: '

Test italic

'}, {input: 'T\n#**Denmark**', expected: '

T
\n#Denmark

'}, {input: 'T\n@**Cordelia Lear**', expected: '

T
\n@Cordelia Lear

'}, {input: '@**Mark Twin|104** and @**Mark Twin|105** are out to confuse you.', expected: '

@Mark Twin and @Mark Twin are out to confuse you.

'}, {input: '@**Invalid User|1234**', expected: '

@**Invalid User|1234**

'}, {input: '@**Cordelia Lear|103** has a wrong user_id.', expected: '

@**Cordelia Lear|103** has a wrong user_id.

'}, {input: '@**Brother of Bobby|123** is really the full name.', expected: '

@Brother of Bobby|123 is really the full name.

'}, {input: '@**Brother of Bobby|123|106**', expected: '

@Brother of Bobby|123

'}, {input: 'T\n@hamletcharacters', expected: '

T
\n@hamletcharacters

'}, {input: 'T\n@*hamletcharacters*', expected: '

T
\n@hamletcharacters

'}, {input: 'T\n@*notagroup*', expected: '

T
\n@*notagroup*

'}, {input: 'T\n@*backend*', expected: '

T
\n@Backend

'}, {input: '@*notagroup*', expected: '

@*notagroup*

'}, {input: 'This is a realm filter `hello` with text after it', expected: '

This is a realm filter hello with text after it

'}, // Test the emoticon conversion {input: ':)', expected: '

:)

'}, {input: ':)', expected: '

:slight_smile:

', translate_emoticons: true}, // Test HTML Escape in Custom Zulip Rules {input: '@**

The Rogue One

**', expected: '

@**<h1>The Rogue One</h1>**

'}, {input: '#**

The Rogue One

**', expected: '

#**<h1>The Rogue One</h1>**

'}, {input: '!avatar(

The Rogue One

)', expected: '

<h1>The Rogue One</h1>

'}, {input: ':

The Rogue One

:', expected: '

:<h1>The Rogue One</h1>:

'}, {input: '@**O\'Connell**', expected: '

@**O'Connell**

'}, {input: '@*Bobby

Tables

*', expected: '

@Bobby <h1>Tables</h1>

'}, {input: '@**Bobby

Tables

**', expected: '

@Bobby <h1>Tables</h1>

'}, {input: '#**Bobby

Tables

**', expected: '

#Bobby <h1 > Tables</h1>

'}, ]; // We remove one of the unicode emoji we put as input in one of the test // cases (U+1F6B2), to verify that we display the emoji as it was input if it // isn't present in emoji_codes.codepoint_to_name. delete emoji_codes.codepoint_to_name['1f6b2']; test_cases.forEach(function (test_case) { // Disable emoji conversion by default. page_params.translate_emoticons = test_case.translate_emoticons || false; const input = test_case.input; const expected = test_case.expected; const message = {raw_content: input}; markdown.apply_markdown(message); const output = message.content; assert.equal(expected, output); }); }); run_test('topic_links', () => { let message = {type: 'stream', topic: "No links here"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 0); message = {type: 'stream', topic: "One #123 link here"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 1); assert.equal(util.get_topic_links(message)[0], "https://trac.zulip.net/ticket/123"); message = {type: 'stream', topic: "Two #123 #456 link here"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 2); assert.equal(util.get_topic_links(message)[0], "https://trac.zulip.net/ticket/123"); assert.equal(util.get_topic_links(message)[1], "https://trac.zulip.net/ticket/456"); message = {type: 'stream', topic: "New ZBUG_123 link here"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 1); assert.equal(util.get_topic_links(message)[0], "https://trac2.zulip.net/ticket/123"); message = {type: 'stream', topic: "New ZBUG_123 with #456 link here"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 2); assert(util.get_topic_links(message).includes("https://trac2.zulip.net/ticket/123")); assert(util.get_topic_links(message).includes("https://trac.zulip.net/ticket/456")); message = {type: 'stream', topic: "One ZGROUP_123:45 link here"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 1); assert.equal(util.get_topic_links(message)[0], "https://zone_45.zulip.net/ticket/123"); message = {type: 'stream', topic: "Hello https://google.com"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 1); assert.equal(util.get_topic_links(message)[0], "https://google.com"); message = {type: 'stream', topic: "#456 https://google.com https://github.com"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 3); assert(util.get_topic_links(message).includes("https://google.com")); assert(util.get_topic_links(message).includes("https://github.com")); assert(util.get_topic_links(message).includes("https://trac.zulip.net/ticket/456")); message = {type: "not-stream"}; markdown.add_topic_links(message); assert.equal(util.get_topic_links(message).length, 0); }); run_test('message_flags', () => { let input = "/me is testing this"; let message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.is_me_message, true); assert(!message.unread); input = "/me is testing\nthis"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.is_me_message, true); input = "testing this @**all** @**Cordelia Lear**"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.is_me_message, false); assert.equal(message.mentioned, true); assert.equal(message.mentioned_me_directly, true); input = "test @**everyone**"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.is_me_message, false); assert.equal(message.mentioned, true); assert.equal(message.mentioned_me_directly, false); input = "test @**stream**"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.is_me_message, false); assert.equal(message.mentioned, true); assert.equal(message.mentioned_me_directly, false); input = "test @all"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.mentioned, false); input = "test @everyone"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.mentioned, false); input = "test @any"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.mentioned, false); input = "test @alleycat.com"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.mentioned, false); input = "test @*hamletcharacters*"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.mentioned, true); input = "test @*backend*"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.mentioned, false); input = "test @**invalid_user**"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); assert.equal(message.mentioned, false); }); run_test('backend_only_realm_filters', () => { const backend_only_realm_filters = [ 'Here is the PR-#123.', 'Function abc() was introduced in (PR)#123.', ]; backend_only_realm_filters.forEach(function (content) { assert.equal(markdown.contains_backend_only_syntax(content), true); }); }); run_test('python_to_js_filter', () => { // The only way to reach python_to_js_filter is indirectly, hence the call // to set_realm_filters. markdown.set_realm_filters([['/a(?im)a/g'], ['/a(?L)a/g']]); let actual_value = marked.InlineLexer.rules.zulip.realm_filters; let expected_value = [/\/aa\/g(?![\w])/gim, /\/aa\/g(?![\w])/g]; assert.deepEqual(actual_value, expected_value); // Test case with multiple replacements. markdown.set_realm_filters([['#cf(?P[0-9]+)(?P[A-Z][0-9A-Z]*)', 'http://google.com']]); actual_value = marked.InlineLexer.rules.zulip.realm_filters; expected_value = [/#cf([0-9]+)([A-Z][0-9A-Z]*)(?![\w])/g]; assert.deepEqual(actual_value, expected_value); // Test incorrect syntax. blueslip.set_test_data('error', 'python_to_js_filter: Invalid regular expression: /!@#@(!#&((!&(@#((?![\\w])/: Unterminated group'); markdown.set_realm_filters([['!@#@(!#&((!&(@#(', 'http://google.com']]); actual_value = marked.InlineLexer.rules.zulip.realm_filters; expected_value = []; assert.deepEqual(actual_value, expected_value); assert.equal(blueslip.get_test_logs('error').length, 1); blueslip.clear_test_data(); }); run_test('katex_throws_unexpected_exceptions', () => { katex.renderToString = function () { throw new Error('some-exception'); }; blueslip.set_test_data('error', 'Error: some-exception'); const message = { raw_content: '$$a$$' }; markdown.apply_markdown(message); assert.equal(blueslip.get_test_logs('error').length, 1); blueslip.clear_test_data(); }); run_test('misc_helpers', () => { const elem = $('.user-mention'); markdown.set_name_in_mention_element(elem, 'Aaron'); assert.equal(elem.text(), '@Aaron'); elem.addClass('silent'); markdown.set_name_in_mention_element(elem, 'Aaron, but silent'); assert.equal(elem.text(), 'Aaron, but silent'); });