2017-11-08 17:42:34 +01:00
zrequire ( 'hash_util' ) ;
2019-07-26 03:47:36 +02:00
set _global ( 'katex' , zrequire ( 'katex' , 'katex/dist/katex.min.js' ) ) ;
set _global ( 'marked' , zrequire ( 'marked' , 'third/marked/lib/marked' ) ) ;
2020-02-04 21:50:55 +01:00
set _global ( 'i18n' , global . stub _i18n ) ;
2017-11-08 17:42:34 +01:00
zrequire ( 'util' ) ;
zrequire ( 'fenced_code' ) ;
zrequire ( 'stream_data' ) ;
zrequire ( 'people' ) ;
2017-11-22 09:11:07 +01:00
zrequire ( 'user_groups' ) ;
2020-02-06 07:07:10 +01:00
const emoji _codes = zrequire ( 'emoji_codes' , 'generated/emoji/emoji_codes.json' ) ;
2017-11-08 17:42:34 +01:00
zrequire ( 'emoji' ) ;
2017-12-16 23:25:31 +01:00
zrequire ( 'message_store' ) ;
2017-11-08 17:42:34 +01:00
zrequire ( 'markdown' ) ;
2014-01-17 21:08:45 +01:00
2019-07-25 09:13:22 +02:00
set _global ( 'location' , {
origin : 'http://zulip.zulipdev.com' ,
2017-04-04 15:31:21 +02:00
} ) ;
2016-10-30 20:54:53 +01:00
set _global ( 'page_params' , {
2017-04-24 21:59:07 +02:00
realm _users : [ ] ,
2016-10-30 20:54:53 +01:00
realm _emoji : {
2018-03-11 18:55:20 +01:00
1 : { id : 1 ,
name : 'burrito' ,
source _url : '/static/generated/emoji/images/emoji/burrito.png' ,
deactivated : false ,
} ,
2016-10-30 20:54:53 +01:00
} ,
realm _filters : [
[
"#(?P<id>[0-9]{2,8})" ,
2016-12-03 23:17:57 +01:00
"https://trac.zulip.net/ticket/%(id)s" ,
2016-10-30 20:54:53 +01:00
] ,
[
"ZBUG_(?P<id>[0-9]{2,8})" ,
2016-12-03 23:17:57 +01:00
"https://trac2.zulip.net/ticket/%(id)s" ,
2016-10-30 20:54:53 +01:00
] ,
[
"ZGROUP_(?P<id>[0-9]{2,8}):(?P<zone>[0-9]{1,8})" ,
2016-12-03 23:17:57 +01:00
"https://zone_%(zone)s.zulip.net/ticket/%(id)s" ,
] ,
] ,
2018-01-15 19:36:32 +01:00
translate _emoticons : false ,
2016-10-30 20:54:53 +01:00
} ) ;
2014-01-24 22:52:37 +01:00
2018-05-01 01:05:25 +02:00
set _global ( 'blueslip' , global . make _zblueslip ( ) ) ;
2017-06-14 11:19:00 +02:00
2017-08-16 22:00:19 +02:00
set _global ( 'Image' , function ( ) {
2018-05-07 03:30:13 +02:00
return { } ;
2017-08-16 22:00:19 +02:00
} ) ;
emoji . initialize ( ) ;
2019-11-02 00:06:25 +01:00
const doc = "" ;
2014-01-16 20:18:55 +01:00
set _global ( 'document' , doc ) ;
2017-06-14 20:30:12 +02:00
set _global ( '$' , global . make _zjquery ( ) ) ;
2013-12-04 17:16:08 +01:00
2014-01-27 18:21:58 +01:00
set _global ( 'feature_flags' , { local _echo : true } ) ;
2019-11-02 00:06:25 +01:00
const people = global . people ;
2016-10-30 20:58:28 +01:00
2019-11-02 00:06:25 +01:00
const cordelia = {
2016-10-30 20:58:28 +01:00
full _name : 'Cordelia Lear' ,
user _id : 101 ,
2016-12-03 23:17:57 +01:00
email : 'cordelia@zulip.com' ,
2017-01-24 01:51:58 +01:00
} ;
people . add ( cordelia ) ;
2014-01-04 01:18:30 +01:00
2017-01-24 00:44:08 +01:00
people . add ( {
full _name : 'Leo' ,
user _id : 102 ,
email : 'leo@zulip.com' ,
} ) ;
2018-03-29 00:25:58 +02:00
people . add ( {
full _name : 'Bobby <h1>Tables</h1>' ,
user _id : 103 ,
email : 'bobby@zulip.com' ,
} ) ;
2018-08-19 03:39:57 +02:00
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' ,
} ) ;
2017-01-24 01:51:58 +01:00
people . initialize _current _user ( cordelia . user _id ) ;
2017-01-24 00:44:08 +01:00
2019-11-02 00:06:25 +01:00
const hamletcharacters = {
2017-11-22 09:11:07 +01:00
name : "hamletcharacters" ,
id : 1 ,
description : "Characters of Hamlet" ,
members : [ cordelia . user _id ] ,
} ;
2019-11-02 00:06:25 +01:00
const backend = {
2017-11-22 09:11:07 +01:00
name : "Backend" ,
id : 2 ,
description : "Backend team" ,
members : [ ] ,
} ;
2019-11-02 00:06:25 +01:00
const edgecase _group = {
2018-03-29 00:25:58 +02:00
name : "Bobby <h1>Tables</h1>" ,
id : 3 ,
description : "HTML Syntax to check for Markdown edge cases." ,
members : [ ] ,
} ;
2017-11-22 09:11:07 +01:00
global . user _groups . add ( hamletcharacters ) ;
global . user _groups . add ( backend ) ;
2018-03-29 00:25:58 +02:00
global . user _groups . add ( edgecase _group ) ;
2017-11-22 09:11:07 +01:00
2019-11-02 00:06:25 +01:00
const stream _data = global . stream _data ;
const denmark = {
2016-11-10 20:20:40 +01:00
subscribed : false ,
color : 'blue' ,
name : 'Denmark' ,
stream _id : 1 ,
2019-05-15 08:54:25 +02:00
is _muted : true ,
2016-11-10 20:20:40 +01:00
} ;
2019-11-02 00:06:25 +01:00
const social = {
2016-11-10 20:20:40 +01:00
subscribed : true ,
color : 'red' ,
name : 'social' ,
stream _id : 2 ,
2019-05-15 08:54:25 +02:00
is _muted : false ,
2016-12-03 23:17:57 +01:00
invite _only : true ,
2016-11-10 20:20:40 +01:00
} ;
2019-11-02 00:06:25 +01:00
const edgecase _stream = {
2018-03-29 00:25:58 +02:00
subscribed : true ,
color : 'green' ,
name : 'Bobby <h1>Tables</h1>' ,
stream _id : 3 ,
2019-05-15 08:54:25 +02:00
is _muted : false ,
2018-03-29 00:25:58 +02:00
} ;
2019-11-02 00:06:25 +01:00
const edgecase _stream _2 = {
2019-06-21 20:47:09 +02:00
subscribed : true ,
color : 'yellow' ,
name : 'Bobby <h1' ,
stream _id : 4 ,
is _muted : false ,
} ;
2020-02-09 22:02:55 +01:00
stream _data . add _sub ( denmark ) ;
stream _data . add _sub ( social ) ;
stream _data . add _sub ( edgecase _stream ) ;
stream _data . add _sub ( edgecase _stream _2 ) ;
2019-06-21 20:47:09 +02:00
// Note: edgecase_stream cannot be mentioned because it is caught by
// streamTopicHandler and it would be parsed as edgecase_stream_2.
2016-11-10 20:20:40 +01:00
2017-06-15 23:48:29 +02:00
// Check the default behavior of fenced code blocks
// works properly before markdown is initialized.
2018-05-15 12:40:07 +02:00
run _test ( 'fenced_block_defaults' , ( ) => {
2019-11-02 00:06:25 +01:00
const input = '\n```\nfenced code\n```\n\nand then after\n' ;
const expected = '\n\n<div class="codehilite"><pre><span></span>fenced code\n</pre></div>\n\n\n\nand then after\n\n' ;
const output = fenced _code . process _fenced _code ( input ) ;
2017-06-15 23:48:29 +02:00
assert . equal ( output , expected ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-06-15 23:48:29 +02:00
2017-05-09 18:01:43 +02:00
markdown . initialize ( ) ;
2013-12-04 17:16:08 +01:00
2019-11-02 00:06:25 +01:00
const bugdown _data = global . read _fixture _data ( 'markdown_test_cases.json' ) ;
2014-01-17 21:08:45 +01:00
2018-05-15 12:40:07 +02:00
run _test ( 'bugdown_detection' , ( ) => {
2019-11-02 00:06:25 +01:00
const no _markup = [
2018-05-07 03:30:13 +02:00
"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" ,
] ;
2013-12-04 17:16:08 +01:00
2019-11-02 00:06:25 +01:00
const markup = [
2018-05-07 03:30:13 +02:00
"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" ,
] ;
2013-12-04 17:16:08 +01:00
no _markup . forEach ( function ( content ) {
2017-07-29 02:51:33 +02:00
assert . equal ( markdown . contains _backend _only _syntax ( content ) , false ) ;
2013-12-04 17:16:08 +01:00
} ) ;
markup . forEach ( function ( content ) {
2017-07-29 02:51:33 +02:00
assert . equal ( markdown . contains _backend _only _syntax ( content ) , true ) ;
2013-12-04 17:16:08 +01:00
} ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2013-12-04 17:16:08 +01:00
2018-05-15 12:40:07 +02:00
run _test ( 'marked_shared' , ( ) => {
2019-11-02 00:06:25 +01:00
const tests = bugdown _data . regular _tests ;
2017-12-10 09:01:37 +01:00
2017-06-13 23:49:50 +02:00
tests . forEach ( function ( test ) {
2018-03-28 10:40:44 +02:00
// Ignore tests if specified
if ( test . ignore === true ) {
return ;
}
2019-11-02 00:06:25 +01:00
const message = { raw _content : test . input } ;
2018-03-25 23:23:22 +02:00
page _params . translate _emoticons = test . translate _emoticons || false ;
2017-06-13 23:49:50 +02:00
markdown . apply _markdown ( message ) ;
2019-11-02 00:06:25 +01:00
const output = message . content ;
const error _message = ` Failure in test: ${ test . name } ` ;
2017-07-29 03:04:17 +02:00
if ( test . marked _expected _output ) {
2018-03-28 09:17:31 +02:00
global . bugdown _assert . notEqual ( test . expected _output , output , error _message ) ;
global . bugdown _assert . equal ( test . marked _expected _output , output , error _message ) ;
2017-07-29 03:17:25 +02:00
} else if ( test . backend _only _rendering ) {
assert . equal ( markdown . contains _backend _only _syntax ( test . input ) , true ) ;
2017-06-13 23:49:50 +02:00
} else {
2018-03-28 09:17:31 +02:00
global . bugdown _assert . equal ( test . expected _output , output , error _message ) ;
2017-06-13 23:49:50 +02:00
}
} ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2014-01-17 21:08:45 +01:00
2018-05-15 12:40:07 +02:00
run _test ( 'message_flags' , ( ) => {
2019-11-02 00:06:25 +01:00
let message = { raw _content : '@**Leo**' } ;
2017-05-09 18:01:43 +02:00
markdown . apply _markdown ( message ) ;
2017-12-16 23:25:31 +01:00
assert ( ! message . mentioned ) ;
assert ( ! message . mentioned _me _directly ) ;
2017-01-24 01:51:58 +01:00
message = { raw _content : '@**Cordelia Lear**' } ;
2017-05-09 18:01:43 +02:00
markdown . apply _markdown ( message ) ;
2017-12-16 23:25:31 +01:00
assert ( message . mentioned ) ;
assert ( message . mentioned _me _directly ) ;
2017-01-24 01:51:58 +01:00
message = { raw _content : '@**all**' } ;
2017-05-09 18:01:43 +02:00
markdown . apply _markdown ( message ) ;
2017-12-16 23:25:31 +01:00
assert ( message . mentioned ) ;
assert ( ! message . mentioned _me _directly ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-01-24 01:51:58 +01:00
2018-05-15 12:40:07 +02:00
run _test ( 'marked' , ( ) => {
2019-11-02 00:06:25 +01:00
const test _cases = [
2017-06-13 23:49:50 +02:00
{ input : 'hello' , expected : '<p>hello</p>' } ,
{ input : 'hello there' , expected : '<p>hello there</p>' } ,
{ input : 'hello **bold** for you' , expected : '<p>hello <strong>bold</strong> for you</p>' } ,
2018-02-17 01:59:22 +01:00
{ input : 'hello ***foo*** for you' , expected : '<p>hello <strong><em>foo</em></strong> for you</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : '__hello__' , expected : '<p>__hello__</p>' } ,
{ input : '\n```\nfenced code\n```\n\nand then after\n' ,
expected : '<div class="codehilite"><pre><span></span>fenced code\n</pre></div>\n\n\n<p>and then after</p>' } ,
2017-07-30 21:28:34 +02:00
{ input : '\n```\n fenced code trailing whitespace \n```\n\nand then after\n' ,
2017-06-13 23:49:50 +02:00
expected : '<div class="codehilite"><pre><span></span> fenced code trailing whitespace\n</pre></div>\n\n\n<p>and then after</p>' } ,
{ input : '* a\n* list \n* here' ,
expected : '<ul>\n<li>a</li>\n<li>list </li>\n<li>here</li>\n</ul>' } ,
2017-06-15 23:48:29 +02:00
{ input : '\n```c#\nfenced code special\n```\n\nand then after\n' ,
expected : '<div class="codehilite"><pre><span></span>fenced code special\n</pre></div>\n\n\n<p>and then after</p>' } ,
{ input : '\n```vb.net\nfenced code dot\n```\n\nand then after\n' ,
expected : '<div class="codehilite"><pre><span></span>fenced code dot\n</pre></div>\n\n\n<p>and then after</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : 'Some text first\n* a\n* list \n* here\n\nand then after' ,
expected : '<p>Some text first</p>\n<ul>\n<li>a</li>\n<li>list </li>\n<li>here</li>\n</ul>\n<p>and then after</p>' } ,
{ input : '1. an\n2. ordered \n3. list' ,
2019-08-11 07:41:34 +02:00
expected : '<ol>\n<li>an</li>\n<li>ordered </li>\n<li>list</li>\n</ol>' } ,
2017-06-13 23:49:50 +02:00
{ input : '\n~~~quote\nquote this for me\n~~~\nthanks\n' ,
expected : '<blockquote>\n<p>quote this for me</p>\n</blockquote>\n<p>thanks</p>' } ,
{ input : 'This is a @**Cordelia Lear** mention' ,
expected : '<p>This is a <span class="user-mention" data-user-id="101">@Cordelia Lear</span> mention</p>' } ,
{ input : 'These @ @**** are not mentions' ,
expected : '<p>These @ @<em>**</em> are not mentions</p>' } ,
{ input : 'These # #**** are not mentions' ,
expected : '<p>These # #<em>**</em> are not mentions</p>' } ,
2017-11-24 07:11:11 +01:00
{ input : 'These @* are not mentions' ,
expected : '<p>These @* are not mentions</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : 'These #* #*** are also not mentions' ,
expected : '<p>These #* #*** are also not mentions</p>' } ,
{ input : 'This is a #**Denmark** stream link' ,
2019-07-12 00:09:38 +02:00
expected : '<p>This is a <a class="stream" data-stream-id="1" href="/#narrow/stream/1-Denmark">#Denmark</a> stream link</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : 'This is #**Denmark** and #**social** stream links' ,
2019-07-12 00:09:38 +02:00
expected : '<p>This is <a class="stream" data-stream-id="1" href="/#narrow/stream/1-Denmark">#Denmark</a> and <a class="stream" data-stream-id="2" href="/#narrow/stream/2-social">#social</a> stream links</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : 'And this is a #**wrong** stream link' ,
expected : '<p>And this is a #**wrong** stream link</p>' } ,
2019-06-21 20:47:09 +02:00
{ input : 'This is a #**Denmark>some topic** stream_topic link' ,
2019-07-12 00:09:38 +02:00
expected : '<p>This is a <a class="stream-topic" data-stream-id="1" href="/#narrow/stream/1-Denmark/topic/some.20topic">#Denmark > some topic</a> stream_topic link</p>' } ,
2019-06-21 20:47:09 +02:00
{ input : 'This has two links: #**Denmark>some topic** and #**social>other topic**.' ,
2019-07-12 00:09:38 +02:00
expected : '<p>This has two links: <a class="stream-topic" data-stream-id="1" href="/#narrow/stream/1-Denmark/topic/some.20topic">#Denmark > some topic</a> and <a class="stream-topic" data-stream-id="2" href="/#narrow/stream/2-social/topic/other.20topic">#social > other topic</a>.</p>' } ,
2019-06-21 20:47:09 +02:00
{ input : 'This is not a #**Denmark>** stream_topic link' ,
expected : '<p>This is not a #**Denmark>** stream_topic link</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : 'mmm...:burrito:s' ,
2017-06-09 10:30:24 +02:00
expected : '<p>mmm...<img alt=":burrito:" class="emoji" src="/static/generated/emoji/images/emoji/burrito.png" title="burrito">s</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : 'This is an :poop: message' ,
2019-01-14 08:45:37 +01:00
expected : '<p>This is an <span aria-label="poop" class="emoji emoji-1f4a9" role="img" title="poop">:poop:</span> message</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : "\ud83d\udca9" ,
2019-01-14 08:45:37 +01:00
expected : '<p><span aria-label="poop" class="emoji emoji-1f4a9" role="img" title="poop">:poop:</span></p>' } ,
2017-09-28 19:25:20 +02:00
{ input : '\u{1f6b2}' ,
expected : '<p>\u{1f6b2}</p>' } ,
2019-02-20 10:15:33 +01:00
{ input : 'Silent mention: @_**Cordelia Lear**' ,
2019-02-15 20:58:54 +01:00
expected : '<p>Silent mention: <span class="user-mention silent" data-user-id="101">Cordelia Lear</span></p>' } ,
2019-01-08 11:30:13 +01:00
{ input : '> Mention in quote: @**Cordelia Lear**\n\nMention outside quote: @**Cordelia Lear**' ,
2019-02-15 20:58:54 +01:00
expected : '<blockquote>\n<p>Mention in quote: <span class="user-mention silent" data-user-id="101">Cordelia Lear</span></p>\n</blockquote>\n<p>Mention outside quote: <span class="user-mention" data-user-id="101">@Cordelia Lear</span></p>' } ,
2017-07-30 21:07:59 +02:00
// Test only those realm filters which don't return True for
// `contains_backend_only_syntax()`. Those which return True
// are tested separately.
2017-06-13 23:49:50 +02:00
{ input : 'This is a realm filter #1234 with text after it' ,
expected : '<p>This is a realm filter <a href="https://trac.zulip.net/ticket/1234" target="_blank" title="https://trac.zulip.net/ticket/1234">#1234</a> with text after it</p>' } ,
2017-07-30 21:07:59 +02:00
{ input : '#1234is not a realm filter.' ,
expected : '<p>#1234is not a realm filter.</p>' } ,
{ input : 'A pattern written as #1234is not a realm filter.' ,
expected : '<p>A pattern written as #1234is not a realm filter.</p>' } ,
2017-06-13 23:49:50 +02:00
{ input : 'This is a realm filter with ZGROUP_123:45 groups' ,
expected : '<p>This is a realm filter with <a href="https://zone_45.zulip.net/ticket/123" target="_blank" title="https://zone_45.zulip.net/ticket/123">ZGROUP_123:45</a> groups</p>' } ,
{ input : 'This is an !avatar(cordelia@zulip.com) of Cordelia Lear' ,
expected : '<p>This is an <img alt="cordelia@zulip.com" class="message_body_gravatar" src="/avatar/cordelia@zulip.com?s=30" title="cordelia@zulip.com"> of Cordelia Lear</p>' } ,
{ input : 'This is a !gravatar(cordelia@zulip.com) of Cordelia Lear' ,
expected : '<p>This is a <img alt="cordelia@zulip.com" class="message_body_gravatar" src="/avatar/cordelia@zulip.com?s=30" title="cordelia@zulip.com"> of Cordelia Lear</p>' } ,
{ input : 'Test *italic*' ,
expected : '<p>Test <em>italic</em></p>' } ,
2017-07-18 08:38:59 +02:00
{ input : 'T\n#**Denmark**' ,
2019-07-12 00:09:38 +02:00
expected : '<p>T<br>\n<a class="stream" data-stream-id="1" href="/#narrow/stream/1-Denmark">#Denmark</a></p>' } ,
2017-07-30 21:28:34 +02:00
{ input : 'T\n@**Cordelia Lear**' ,
2018-05-07 03:30:13 +02:00
expected : '<p>T<br>\n<span class="user-mention" data-user-id="101">@Cordelia Lear</span></p>' } ,
2018-08-19 03:39:57 +02:00
{ input : '@**Mark Twin|104** and @**Mark Twin|105** are out to confuse you.' ,
2018-12-18 19:34:45 +01:00
expected : '<p><span class="user-mention" data-user-id="104">@Mark Twin</span> and <span class="user-mention" data-user-id="105">@Mark Twin</span> are out to confuse you.</p>' } ,
2018-08-19 03:39:57 +02:00
{ input : '@**Invalid User|1234**' ,
expected : '<p>@**Invalid User|1234**</p>' } ,
{ input : '@**Cordelia Lear|103** has a wrong user_id.' ,
expected : '<p>@**Cordelia Lear|103** has a wrong user_id.</p>' } ,
{ input : '@**Brother of Bobby|123** is really the full name.' ,
expected : '<p><span class="user-mention" data-user-id="106">@Brother of Bobby|123</span> is really the full name.</p>' } ,
{ input : '@**Brother of Bobby|123|106**' ,
expected : '<p><span class="user-mention" data-user-id="106">@Brother of Bobby|123</span></p>' } ,
2017-11-22 09:11:07 +01:00
{ input : 'T\n@hamletcharacters' ,
expected : '<p>T<br>\n@hamletcharacters</p>' } ,
{ input : 'T\n@*hamletcharacters*' ,
expected : '<p>T<br>\n<span class="user-group-mention" data-user-group-id="1">@hamletcharacters</span></p>' } ,
2017-11-29 06:42:47 +01:00
{ input : 'T\n@*notagroup*' ,
expected : '<p>T<br>\n@*notagroup*</p>' } ,
2018-05-07 03:30:13 +02:00
{ input : 'T\n@*backend*' ,
2017-11-22 09:11:07 +01:00
expected : '<p>T<br>\n<span class="user-group-mention" data-user-group-id="2">@Backend</span></p>' } ,
{ input : '@*notagroup*' ,
expected : '<p>@*notagroup*</p>' } ,
2017-07-18 08:38:59 +02:00
{ input : 'This is a realm filter `hello` with text after it' ,
expected : '<p>This is a realm filter <code>hello</code> with text after it</p>' } ,
2018-03-25 18:10:59 +02:00
// Test the emoticon conversion
{ input : ':)' ,
expected : '<p>:)</p>' } ,
{ input : ':)' ,
2019-01-14 08:45:37 +01:00
expected : '<p><span aria-label="slight smile" class="emoji emoji-1f642" role="img" title="slight smile">:slight_smile:</span></p>' ,
2018-03-25 18:10:59 +02:00
translate _emoticons : true } ,
2018-05-07 03:30:13 +02:00
// Test HTML Escape in Custom Zulip Rules
2018-03-29 00:25:58 +02:00
{ input : '@**<h1>The Rogue One</h1>**' ,
expected : '<p>@**<h1>The Rogue One</h1>**</p>' } ,
{ input : '#**<h1>The Rogue One</h1>**' ,
expected : '<p>#**<h1>The Rogue One</h1>**</p>' } ,
{ input : '!avatar(<h1>The Rogue One</h1>)' ,
expected : '<p><img alt="<h1>The Rogue One</h1>" class="message_body_gravatar" src="/avatar/<h1>The Rogue One</h1>?s=30" title="<h1>The Rogue One</h1>"></p>' } ,
{ input : ':<h1>The Rogue One</h1>:' ,
expected : '<p>:<h1>The Rogue One</h1>:</p>' } ,
{ input : '@**O\'Connell**' ,
expected : '<p>@**O'Connell**</p>' } ,
{ input : '@*Bobby <h1>Tables</h1>*' ,
expected : '<p><span class="user-group-mention" data-user-group-id="3">@Bobby <h1>Tables</h1></span></p>' } ,
{ input : '@**Bobby <h1>Tables</h1>**' ,
expected : '<p><span class="user-mention" data-user-id="103">@Bobby <h1>Tables</h1></span></p>' } ,
{ input : '#**Bobby <h1>Tables</h1>**' ,
2019-07-12 00:09:38 +02:00
expected : '<p><a class="stream-topic" data-stream-id="4" href="/#narrow/stream/4-Bobby-.3Ch1/topic/Tables.3C.2Fh1.3E">#Bobby <h1 > Tables</h1></a></p>' } ,
2017-06-13 23:49:50 +02:00
] ;
// We remove one of the unicode emoji we put as input in one of the test
2017-09-28 19:25:20 +02:00
// 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' ] ;
2017-06-13 23:49:50 +02:00
test _cases . forEach ( function ( test _case ) {
2018-03-25 18:10:59 +02:00
// Disable emoji conversion by default.
page _params . translate _emoticons = test _case . translate _emoticons || false ;
2019-11-02 00:06:25 +01:00
const input = test _case . input ;
const expected = test _case . expected ;
2017-06-13 23:49:50 +02:00
2019-11-02 00:06:25 +01:00
const message = { raw _content : input } ;
2017-06-13 23:49:50 +02:00
markdown . apply _markdown ( message ) ;
2019-11-02 00:06:25 +01:00
const output = message . content ;
2017-06-13 23:49:50 +02:00
assert . equal ( expected , output ) ;
} ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2014-01-24 21:48:56 +01:00
2018-11-15 16:41:21 +01:00
run _test ( 'topic_links' , ( ) => {
2019-11-02 00:06:25 +01:00
let message = { type : 'stream' , topic : "No links here" } ;
2018-11-13 16:19:59 +01:00
markdown . add _topic _links ( message ) ;
2020-02-12 11:49:02 +01:00
assert . equal ( util . get _topic _links ( message ) . length , 0 ) ;
2017-06-13 23:49:50 +02:00
2018-12-23 16:49:14 +01:00
message = { type : 'stream' , topic : "One #123 link here" } ;
2018-11-13 16:19:59 +01:00
markdown . add _topic _links ( message ) ;
2018-11-15 16:41:21 +01:00
assert . equal ( util . get _topic _links ( message ) . length , 1 ) ;
assert . equal ( util . get _topic _links ( message ) [ 0 ] , "https://trac.zulip.net/ticket/123" ) ;
2017-06-13 23:49:50 +02:00
2018-12-23 16:49:14 +01:00
message = { type : 'stream' , topic : "Two #123 #456 link here" } ;
2018-11-13 16:19:59 +01:00
markdown . add _topic _links ( message ) ;
2018-11-15 16:41:21 +01:00
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" ) ;
2017-06-13 23:49:50 +02:00
2018-12-23 16:49:14 +01:00
message = { type : 'stream' , topic : "New ZBUG_123 link here" } ;
2018-11-13 16:19:59 +01:00
markdown . add _topic _links ( message ) ;
2018-11-15 16:41:21 +01:00
assert . equal ( util . get _topic _links ( message ) . length , 1 ) ;
assert . equal ( util . get _topic _links ( message ) [ 0 ] , "https://trac2.zulip.net/ticket/123" ) ;
2017-06-13 23:49:50 +02:00
2018-12-23 16:49:14 +01:00
message = { type : 'stream' , topic : "New ZBUG_123 with #456 link here" } ;
2018-11-13 16:19:59 +01:00
markdown . add _topic _links ( message ) ;
2018-11-15 16:41:21 +01:00
assert . equal ( util . get _topic _links ( message ) . length , 2 ) ;
js: Convert a.indexOf(…) !== -1 to a.includes(…).
Babel polyfills this for us for Internet Explorer.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import K from "ast-types/gen/kinds";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
recast.visit(ast, {
visitBinaryExpression(path) {
const { operator, left, right } = path.node;
if (
n.CallExpression.check(left) &&
n.MemberExpression.check(left.callee) &&
!left.callee.computed &&
n.Identifier.check(left.callee.property) &&
left.callee.property.name === "indexOf" &&
left.arguments.length === 1 &&
checkExpression(left.arguments[0]) &&
((["===", "!==", "==", "!=", ">", "<="].includes(operator) &&
n.UnaryExpression.check(right) &&
right.operator == "-" &&
n.Literal.check(right.argument) &&
right.argument.value === 1) ||
([">=", "<"].includes(operator) &&
n.Literal.check(right) &&
right.value === 0))
) {
const test = b.callExpression(
b.memberExpression(left.callee.object, b.identifier("includes")),
[left.arguments[0]]
);
path.replace(
["!==", "!=", ">", ">="].includes(operator)
? test
: b.unaryExpression("!", test)
);
changed = true;
}
this.traverse(path);
},
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-08 04:55:06 +01:00
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" ) ) ;
2017-06-13 23:49:50 +02:00
2018-12-23 16:49:14 +01:00
message = { type : 'stream' , topic : "One ZGROUP_123:45 link here" } ;
2018-11-13 16:19:59 +01:00
markdown . add _topic _links ( message ) ;
2018-11-15 16:41:21 +01:00
assert . equal ( util . get _topic _links ( message ) . length , 1 ) ;
assert . equal ( util . get _topic _links ( message ) [ 0 ] , "https://zone_45.zulip.net/ticket/123" ) ;
2017-06-13 23:49:50 +02:00
2019-05-25 16:10:30 +02:00
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 ) ;
js: Convert a.indexOf(…) !== -1 to a.includes(…).
Babel polyfills this for us for Internet Explorer.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import K from "ast-types/gen/kinds";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
recast.visit(ast, {
visitBinaryExpression(path) {
const { operator, left, right } = path.node;
if (
n.CallExpression.check(left) &&
n.MemberExpression.check(left.callee) &&
!left.callee.computed &&
n.Identifier.check(left.callee.property) &&
left.callee.property.name === "indexOf" &&
left.arguments.length === 1 &&
checkExpression(left.arguments[0]) &&
((["===", "!==", "==", "!=", ">", "<="].includes(operator) &&
n.UnaryExpression.check(right) &&
right.operator == "-" &&
n.Literal.check(right.argument) &&
right.argument.value === 1) ||
([">=", "<"].includes(operator) &&
n.Literal.check(right) &&
right.value === 0))
) {
const test = b.callExpression(
b.memberExpression(left.callee.object, b.identifier("includes")),
[left.arguments[0]]
);
path.replace(
["!==", "!=", ">", ">="].includes(operator)
? test
: b.unaryExpression("!", test)
);
changed = true;
}
this.traverse(path);
},
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-08 04:55:06 +01:00
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" ) ) ;
2019-05-25 16:10:30 +02:00
2017-06-13 23:49:50 +02:00
message = { type : "not-stream" } ;
2018-11-13 16:19:59 +01:00
markdown . add _topic _links ( message ) ;
2018-11-15 16:41:21 +01:00
assert . equal ( util . get _topic _links ( message ) . length , 0 ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2014-01-27 17:06:59 +01:00
2018-05-15 12:40:07 +02:00
run _test ( 'message_flags' , ( ) => {
2019-11-02 00:06:25 +01:00
let input = "/me is testing this" ;
let message = { topic : "No links here" , raw _content : input } ;
2017-06-13 23:49:50 +02:00
markdown . apply _markdown ( message ) ;
2017-08-27 18:10:36 +02:00
assert . equal ( message . is _me _message , true ) ;
2017-12-16 23:25:31 +01:00
assert ( ! message . unread ) ;
2017-06-13 23:49:50 +02:00
2018-01-21 19:27:36 +01:00
input = "/me is testing\nthis" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2018-01-21 19:27:36 +01:00
markdown . apply _markdown ( message ) ;
2018-12-29 11:07:27 +01:00
assert . equal ( message . is _me _message , true ) ;
2018-01-21 19:27:36 +01:00
2017-06-13 23:49:50 +02:00
input = "testing this @**all** @**Cordelia Lear**" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2017-06-13 23:49:50 +02:00
markdown . apply _markdown ( message ) ;
2017-08-27 18:10:36 +02:00
assert . equal ( message . is _me _message , false ) ;
2017-12-16 23:25:31 +01:00
assert . equal ( message . mentioned , true ) ;
assert . equal ( message . mentioned _me _directly , true ) ;
2017-06-13 23:49:50 +02:00
2018-04-03 17:55:57 +02:00
input = "test @**everyone**" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2018-04-03 17:55:57 +02:00
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**" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2018-04-03 17:55:57 +02:00
markdown . apply _markdown ( message ) ;
assert . equal ( message . is _me _message , false ) ;
assert . equal ( message . mentioned , true ) ;
assert . equal ( message . mentioned _me _directly , false ) ;
2017-06-13 23:49:50 +02:00
input = "test @all" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2017-06-13 23:49:50 +02:00
markdown . apply _markdown ( message ) ;
2018-01-24 17:18:07 +01:00
assert . equal ( message . mentioned , false ) ;
input = "test @everyone" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2018-01-24 17:18:07 +01:00
markdown . apply _markdown ( message ) ;
assert . equal ( message . mentioned , false ) ;
2017-06-13 23:49:50 +02:00
input = "test @any" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2017-06-13 23:49:50 +02:00
markdown . apply _markdown ( message ) ;
2017-12-16 23:25:31 +01:00
assert . equal ( message . mentioned , false ) ;
2017-11-22 09:11:07 +01:00
2018-01-24 17:18:07 +01:00
input = "test @alleycat.com" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2018-01-24 17:18:07 +01:00
markdown . apply _markdown ( message ) ;
assert . equal ( message . mentioned , false ) ;
2017-11-22 09:11:07 +01:00
input = "test @*hamletcharacters*" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2017-11-22 09:11:07 +01:00
markdown . apply _markdown ( message ) ;
2017-12-16 23:25:31 +01:00
assert . equal ( message . mentioned , true ) ;
2017-11-22 09:11:07 +01:00
input = "test @*backend*" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2017-11-22 09:11:07 +01:00
markdown . apply _markdown ( message ) ;
2017-12-16 23:25:31 +01:00
assert . equal ( message . mentioned , false ) ;
2018-01-24 17:18:07 +01:00
input = "test @**invalid_user**" ;
2018-12-23 16:49:14 +01:00
message = { topic : "No links here" , raw _content : input } ;
2018-01-24 17:18:07 +01:00
markdown . apply _markdown ( message ) ;
assert . equal ( message . mentioned , false ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-06-13 23:26:27 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'backend_only_realm_filters' , ( ) => {
2019-11-02 00:06:25 +01:00
const backend _only _realm _filters = [
2017-07-30 21:07:59 +02:00
'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 ) ;
} ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-07-30 21:07:59 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'python_to_js_filter' , ( ) => {
2017-06-13 23:49:50 +02:00
// The only way to reach python_to_js_filter is indirectly, hence the call
// to set_realm_filters.
2017-10-06 21:43:12 +02:00
markdown . set _realm _filters ( [ [ '/a(?im)a/g' ] , [ '/a(?L)a/g' ] ] ) ;
2019-11-02 00:06:25 +01:00
let actual _value = marked . InlineLexer . rules . zulip . realm _filters ;
let expected _value = [ /\/aa\/g(?![\w])/gim , /\/aa\/g(?![\w])/g ] ;
2017-06-13 23:49:50 +02:00
assert . deepEqual ( actual _value , expected _value ) ;
2019-02-11 22:54:18 +01:00
// Test case with multiple replacements.
markdown . set _realm _filters ( [ [ '#cf(?P<contest>[0-9]+)(?P<problem>[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 ) ;
2019-02-12 22:30:57 +01:00
// 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 ) ;
2019-02-15 23:38:59 +01:00
assert . equal ( blueslip . get _test _logs ( 'error' ) . length , 1 ) ;
2019-02-12 22:30:57 +01:00
blueslip . clear _test _data ( ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2017-06-14 11:19:00 +02:00
2018-05-15 12:40:07 +02:00
run _test ( 'katex_throws_unexpected_exceptions' , ( ) => {
2017-06-14 11:19:00 +02:00
katex . renderToString = function ( ) { throw new Error ( 'some-exception' ) ; } ;
2018-05-01 01:05:25 +02:00
blueslip . set _test _data ( 'error' , 'Error: some-exception' ) ;
2019-11-02 00:06:25 +01:00
const message = { raw _content : '$$a$$' } ;
2017-06-14 11:19:00 +02:00
markdown . apply _markdown ( message ) ;
2019-02-15 23:38:59 +01:00
assert . equal ( blueslip . get _test _logs ( 'error' ) . length , 1 ) ;
2018-05-01 01:05:25 +02:00
blueslip . clear _test _data ( ) ;
2018-05-15 12:40:07 +02:00
} ) ;
2019-02-15 23:24:26 +01:00
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' ) ;
} ) ;