import { buildQuote } from "discourse/lib/quote"; import Post from "discourse/models/post"; import PrettyText, { buildOptions } from "pretty-text/pretty-text"; import { IMAGE_VERSION as v } from "pretty-text/emoji/version"; import { applyCachedInlineOnebox, deleteCachedInlineOnebox, } from "pretty-text/inline-oneboxer"; import { extractDataAttribute } from "pretty-text/engines/discourse-markdown-it"; import { registerEmoji } from "pretty-text/emoji"; import { deepMerge } from "discourse-common/lib/object"; QUnit.module("lib:pretty-text"); const rawOpts = { siteSettings: { enable_emoji: true, enable_emoji_shortcuts: true, enable_mentions: true, emoji_set: "emoji_one", highlighted_languages: "json|ruby|javascript", default_code_lang: "auto", enable_markdown_linkify: true, markdown_linkify_tlds: "com", }, getURL: (url) => url, }; const defaultOpts = buildOptions(rawOpts); QUnit.assert.cooked = function (input, expected, message) { const actual = new PrettyText(defaultOpts).cook(input); this.pushResult({ result: actual === expected.replace(/\/>/g, ">"), actual, expected, message, }); }; QUnit.assert.cookedOptions = function (input, opts, expected, message) { const merged = deepMerge({}, rawOpts, opts); const actual = new PrettyText(buildOptions(merged)).cook(input); this.pushResult({ result: actual === expected, actual, expected, message, }); }; QUnit.assert.cookedPara = function (input, expected, message) { QUnit.assert.cooked(input, `

${expected}

`, message); }; QUnit.skip("Pending Engine fixes and spec fixes", (assert) => { assert.cooked( "Derpy: http://derp.com?_test_=1", '

Derpy: http://derp.com?_test_=1

', "works with underscores in urls" ); assert.cooked( "**a*_b**", "

a*_b

", "allows for characters within bold" ); }); QUnit.test("buildOptions", (assert) => { assert.ok( buildOptions({ siteSettings: { enable_emoji: true } }).discourse.features .emoji, "emoji enabled" ); assert.ok( !buildOptions({ siteSettings: { enable_emoji: false } }).discourse.features .emoji, "emoji disabled" ); }); QUnit.test("basic cooking", (assert) => { assert.cooked("hello", "

hello

", "surrounds text with paragraphs"); assert.cooked("**evil**", "

evil

", "it bolds text."); assert.cooked("__bold__", "

bold

", "it bolds text."); assert.cooked("*trout*", "

trout

", "it italicizes text."); assert.cooked("_trout_", "

trout

", "it italicizes text."); assert.cooked( "***hello***", "

hello

", "it can do bold and italics at once." ); assert.cooked( "word_with_underscores", "

word_with_underscores

", "it doesn't do intraword italics" ); assert.cooked( "common/_special_font_face.html.erb", "

common/_special_font_face.html.erb

", "it doesn't intraword with a slash" ); assert.cooked( "hello \\*evil\\*", "

hello *evil*

", "it supports escaping of asterisks" ); assert.cooked( "hello \\_evil\\_", "

hello _evil_

", "it supports escaping of italics" ); assert.cooked( "brussels sprouts are *awful*.", "

brussels sprouts are awful.

", "it doesn't swallow periods." ); }); QUnit.test("Nested bold and italics", (assert) => { assert.cooked( "*this is italic **with some bold** inside*", "

this is italic with some bold inside

", "it handles nested bold in italics" ); }); QUnit.test("Traditional Line Breaks", (assert) => { const input = "1\n2\n3"; assert.cooked( input, "

1
\n2
\n3

", "automatically handles trivial newlines" ); assert.cookedOptions( input, { siteSettings: { traditional_markdown_linebreaks: true } }, "

1\n2\n3

" ); }); QUnit.test("Unbalanced underscores", (assert) => { assert.cooked( "[evil_trout][1] hello_\n\n[1]: http://eviltrout.com", '

evil_trout hello_

' ); }); QUnit.test("Line Breaks", (assert) => { assert.cooked( "[] first choice\n[] second choice", "

[] first choice
\n[] second choice

", "it handles new lines correctly with [] options" ); // note this is a change from previous engine but is correct // we have an html block and behavior is defined per common mark // spec // ole engine would wrap trout in a

assert.cooked( "

evil
\ntrout", "
evil
\ntrout", "it doesn't insert
after blockquotes" ); assert.cooked( "leading
evil
\ntrout", "

leading

evil

\ntrout

", "it doesn't insert
after blockquotes with leading text" ); }); QUnit.test("Paragraphs for HTML", (assert) => { assert.cooked( "
hello world
", "
hello world
", "it doesn't surround
with paragraphs" ); assert.cooked( "

hello world

", "

hello world

", "it doesn't surround

with paragraphs" ); assert.cooked( "hello world", "

hello world

", "it surrounds inline html tags with paragraphs" ); assert.cooked( "hello world", "

hello world

", "it surrounds inline html tags with paragraphs" ); }); QUnit.test("Links", (assert) => { assert.cooked( "EvilTrout: http://eviltrout.com", '

EvilTrout: http://eviltrout.com

', "autolinks a URL" ); const link = "http://www.youtube.com/watch?v=1MrpeBRkM5A"; assert.cooked( `Youtube: ${link}`, `

Youtube: ${link}

`, "allows links to contain query params" ); try { applyCachedInlineOnebox(link, {}); assert.cooked( `Youtube: ${link}`, `

Youtube: ${link}

` ); } finally { deleteCachedInlineOnebox(link); } assert.cooked( "Derpy: http://derp.com?__test=1", `

Derpy: http://derp.com?__test=1

`, "works with double underscores in urls" ); assert.cooked( "Atwood: www.codinghorror.com", '

Atwood: www.codinghorror.com

', "autolinks something that begins with www" ); assert.cooked( "Atwood: http://www.codinghorror.com", '

Atwood: http://www.codinghorror.com

', "autolinks a URL with http://www" ); assert.cooked( "EvilTrout: http://eviltrout.com hello", '

EvilTrout: http://eviltrout.com hello

', "autolinks with trailing text" ); assert.cooked( "here is [an example](http://twitter.com)", '

here is an example

', "supports markdown style links" ); assert.cooked( "Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)", `

Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)

`, "autolinks a URL with parentheses (like Wikipedia)" ); assert.cooked( "Here's a tweet:\nhttps://twitter.com/evil_trout/status/345954894420787200", '

Here\'s a tweet:
\nhttps://twitter.com/evil_trout/status/345954894420787200

', "It doesn't strip the new line." ); assert.cooked( "1. View @eviltrout's profile here: http://meta.discourse.org/u/eviltrout/activity
next line.", `
    \n
  1. View @eviltrout\'s profile here: http://meta.discourse.org/u/eviltrout/activity
    next line.
  2. \n
`, "allows autolinking within a list without inserting a paragraph." ); assert.cooked( "[3]: http://eviltrout.com", "", "It doesn't autolink markdown link references" ); assert.cooked( "[]: http://eviltrout.com", '

[]: http://eviltrout.com

', "It doesn't accept empty link references" ); assert.cooked( "[b]label[/b]: description", '

label: description

', "It doesn't accept BBCode as link references" ); assert.cooked( "http://discourse.org and http://discourse.org/another_url and http://www.imdb.com/name/nm2225369", '

http://discourse.org and ' + `http://discourse.org/another_url and ` + `http://www.imdb.com/name/nm2225369

`, "allows multiple links on one line" ); assert.cooked( "* [Evil Trout][1]\n\n[1]: http://eviltrout.com", '', "allows markdown link references in a list" ); assert.cooked( "User [MOD]: Hello!", "

User [MOD]: Hello!

", "It does not consider references that are obviously not URLs" ); assert.cooked( "http://eviltrout.com", '

http://eviltrout.com

', "Links within HTML tags" ); assert.cooked( "[http://google.com ... wat](http://discourse.org)", '

http://google.com ... wat

', "it supports links within links" ); assert.cooked( "[http://google.com](http://discourse.org)", '

http://google.com

', "it supports markdown links where the name and link match" ); assert.cooked( '[Link](http://www.example.com) (with an outer "description")', '

Link (with an outer "description")

', "it doesn't consume closing parens as part of the url" ); assert.cooked( "A link inside parentheses (http://www.example.com)", '

A link inside parentheses (http://www.example.com)

', "it auto-links a url within parentheses" ); assert.cooked( "[ul][1]\n\n[1]: http://eviltrout.com", '

ul

', "it can use `ul` as a link name" ); }); QUnit.test("simple quotes", (assert) => { assert.cooked( "> nice!", "
\n

nice!

\n
", "it supports simple quotes" ); assert.cooked( " > nice!", "
\n

nice!

\n
", "it allows quotes with preceding spaces" ); assert.cooked( "> level 1\n> > level 2", "
\n

level 1

\n
\n

level 2

\n
\n
", "it allows nesting of blockquotes" ); assert.cooked( "> level 1\n> > level 2", "
\n

level 1

\n
\n

level 2

\n
\n
", "it allows nesting of blockquotes with spaces" ); assert.cooked( "- hello\n\n > world\n > eviltrout", `
  • hello

    world
    eviltrout

`, "it allows quotes within a list." ); assert.cooked( "-

eviltrout

", "
    \n
  • \n

    eviltrout

  • \n
", "it allows paragraphs within a list." ); assert.cooked( " > indent 1\n > indent 2", "
\n

indent 1
\nindent 2

\n
", "allow multiple spaces to indent" ); }); QUnit.test("Quotes", (assert) => { assert.cookedOptions( '[quote="eviltrout, post: 1"]\na quote\n\nsecond line\n\nthird line\n[/quote]', { topicId: 2 }, ``, "works with multiple lines" ); assert.cookedOptions( '[quote="bob, post:1"]\nmy quote\n[/quote]', { topicId: 2, lookupAvatar: function () {} }, ``, "includes no avatar if none is found" ); assert.cooked( `[quote]\na\n\n[quote]\nb\n[/quote]\n[/quote]`, ``, "handles nested quotes properly" ); assert.cookedOptions( `[quote="bob, post:1, topic:1"]\ntest quote\n[/quote]`, { lookupPrimaryUserGroupByPostNumber: () => "aUserGroup" }, ``, "quote has group class" ); }); QUnit.test("Mentions", (assert) => { assert.cooked( "Hello @sam", '

Hello @sam

', "translates mentions to links" ); assert.cooked( "[@codinghorror](https://twitter.com/codinghorror)", '

@codinghorror

', "it doesn't do mentions within links" ); assert.cooked( "[@codinghorror](https://twitter.com/codinghorror)", '

@codinghorror

', "it doesn't do link mentions within links" ); assert.cooked( "Hello @EvilTrout", '

Hello @EvilTrout

', "adds a mention class" ); assert.cooked( "robin@email.host", "

robin@email.host

", "won't add mention class to an email address" ); assert.cooked( "hanzo55@yahoo.com", '

hanzo55@yahoo.com

', "won't be affected by email addresses that have a number before the @ symbol" ); assert.cooked( "@EvilTrout yo", '

@EvilTrout yo

', "it handles mentions at the beginning of a string" ); assert.cooked( "yo\n@EvilTrout", '

yo
\n@EvilTrout

', "it handles mentions at the beginning of a new line" ); assert.cooked( "`evil` @EvilTrout `trout`", '

evil @EvilTrout trout

', "deals correctly with multiple blocks" ); assert.cooked( "```\na @test\n```", '
a @test\n
', "should not do mentions within a code block." ); assert.cooked( "> foo bar baz @eviltrout", '
\n

foo bar baz @eviltrout

\n
', "handles mentions in simple quotes" ); assert.cooked( "> foo bar baz @eviltrout ohmagerd\nlook at this", '
\n

foo bar baz @eviltrout ohmagerd
\nlook at this

\n
', "does mentions properly with trailing text within a simple quote" ); assert.cooked( "`code` is okay before @mention", '

code is okay before @mention

', "Does not mention in an inline code block" ); assert.cooked( "@mention is okay before `code`", '

@mention is okay before code

', "Does not mention in an inline code block" ); assert.cooked( "don't `@mention`", "

don't @mention

", "Does not mention in an inline code block" ); assert.cooked( "Yes `@this` should be code @eviltrout", '

Yes @this should be code @eviltrout

', "Does not mention in an inline code block" ); assert.cooked( "@eviltrout and `@eviltrout`", '

@eviltrout and @eviltrout

', "you can have a mention in an inline code block following a real mention." ); assert.cooked( "1. this is a list\n\n2. this is an @eviltrout mention\n", '
    \n
  1. \n

    this is a list

    \n
  2. \n
  3. \n

    this is an @eviltrout mention

    \n
  4. \n
', "it mentions properly in a list." ); assert.cooked( "Hello @foo/@bar", '

Hello @foo/@bar

', "handles mentions separated by a slash." ); assert.cooked( "a @sam c", '

a @sam c

', "it allows mentions within HTML tags" ); assert.cooked( "@_sam @1sam @ab-cd.123_ABC-xYz @sam1", '

@_sam @1sam @ab-cd.123_ABC-xYz @sam1

', "it detects mentions of valid usernames" ); assert.cooked( "@.sam @-sam @sam. @sam_ @sam-", '

@.sam @-sam @sam. @sam_ @sam-

', "it does not detect mentions of invalid usernames" ); assert.cookedOptions( "Hello @狮子", { siteSettings: { unicode_usernames: false } }, "

Hello @狮子

", "it does not detect mentions of Unicode usernames" ); }); QUnit.test("Mentions - Unicode usernames enabled", (assert) => { assert.cookedOptions( "Hello @狮子", { siteSettings: { unicode_usernames: true } }, '

Hello @狮子

', "it detects mentions of Unicode usernames" ); assert.cookedOptions( "@狮子 @_狮子 @1狮子 @狮-ø.١٢٣_Ö-ழ் @狮子1", { siteSettings: { unicode_usernames: true } }, '

@狮子 @_狮子 @1狮子 @狮-ø.١٢٣_Ö-ழ் @狮子1

', "it detects mentions of valid Unicode usernames" ); assert.cookedOptions( "@.狮子 @-狮子 @狮子. @狮子_ @狮子-", { siteSettings: { unicode_usernames: true } }, '

@.狮子 @-狮子 @狮子. @狮子_ @狮子-

', "it does not detect mentions of invalid Unicode usernames" ); }); QUnit.test("Mentions - disabled", (assert) => { assert.cookedOptions( "@eviltrout", { siteSettings: { enable_mentions: false } }, "

@eviltrout

" ); }); QUnit.test("Category hashtags", (assert) => { const alwaysTrue = { categoryHashtagLookup: function () { return ["http://test.discourse.org/category-hashtag", "category-hashtag"]; }, }; assert.cookedOptions( "Check out #category-hashtag", alwaysTrue, '

Check out #category-hashtag

', "it translates category hashtag into links" ); assert.cooked( "Check out #category-hashtag", '

Check out #category-hashtag

', "it does not translate category hashtag into links if it is not a valid category hashtag" ); assert.cookedOptions( "[#category-hashtag](http://www.test.com)", alwaysTrue, '

#category-hashtag

', "it does not translate category hashtag within links" ); assert.cooked( "```\n# #category-hashtag\n```", '
# #category-hashtag\n
', "it does not translate category hashtags to links in code blocks" ); assert.cooked( "># #category-hashtag\n", '
\n

#category-hashtag

\n
', "it handles category hashtags in simple quotes" ); assert.cooked( "# #category-hashtag", '

#category-hashtag

', "it works within ATX-style headers" ); assert.cooked( "don't `#category-hashtag`", "

don't #category-hashtag

", "it does not mention in an inline code block" ); assert.cooked( "#category-hashtag", '

#category-hashtag

', "it works between HTML tags" ); assert.cooked( "Checkout #ụdị", '

Checkout #ụdị

', "it works for non-english characters" ); }); QUnit.test("Heading", (assert) => { assert.cooked( "**Bold**\n----------", "

Bold

", "It will bold the heading" ); }); QUnit.test("bold and italics", (assert) => { assert.cooked( 'a "**hello**"', "

a "hello"

", "bolds in quotes" ); assert.cooked( "(**hello**)", "

(hello)

", "bolds in parens" ); assert.cooked( "**hello**\nworld", "

hello
\nworld

", "allows newline after bold" ); assert.cooked( "**hello**\n**world**", "

hello
\nworld

", "newline between two bolds" ); assert.cooked( "** hello**", "

** hello**

", "does not bold on a space boundary" ); assert.cooked( "**hello **", "

**hello **

", "does not bold on a space boundary" ); assert.cooked( "**你hello**", "

你hello

", "allows bolded chinese" ); }); QUnit.test("Escaping", (assert) => { assert.cooked( "*\\*laughs\\**", "

*laughs*

", "allows escaping strong" ); assert.cooked( "*\\_laughs\\_*", "

_laughs_

", "allows escaping em" ); }); QUnit.test("New Lines", (assert) => { // historically we would not continue inline em or b across lines, // however commonmark gives us no switch to do so and we would be very non compliant. // turning softbreaks into a newline is just a renderer option, not a parser switch. assert.cooked( "_abc\ndef_", "

abc
\ndef

", "it does allow inlines to span new lines" ); assert.cooked( "_abc\n\ndef_", "

_abc

\n

def_

", "it does not allow inlines to span new paragraphs" ); }); QUnit.test("Oneboxing", (assert) => { function matches(input, regexp) { return new PrettyText(defaultOpts).cook(input).match(regexp); } assert.ok( !matches( "- http://www.textfiles.com/bbs/MINDVOX/FORUMS/ethics\n\n- http://drupal.org", /class="onebox"/ ), "doesn't onebox a link within a list" ); assert.ok( matches("http://test.com", /class="onebox"/), "adds a onebox class to a link on its own line" ); assert.ok( matches("http://test.com\nhttp://test2.com", /onebox[\s\S]+onebox/m), "supports multiple links" ); assert.ok( !matches("http://test.com bob", /onebox/), "doesn't onebox links that have trailing text" ); assert.ok( !matches("[Tom Cruise](http://www.tomcruise.com/)", "onebox"), "Markdown links with labels are not oneboxed" ); assert.ok( !matches( "[http://www.tomcruise.com/](http://www.tomcruise.com/)", "onebox" ), "Markdown links where the label is the same as the url but link is explicit" ); assert.cooked( "http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street", '

http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street

', "works with links that have underscores in them" ); }); QUnit.test("links with full urls", (assert) => { assert.cooked( "[http://eviltrout.com][1] is a url\n\n[1]: http://eviltrout.com", '

http://eviltrout.com is a url

', "it supports links that are full URLs" ); }); QUnit.test("Code Blocks", (assert) => { assert.cooked( "
\nhello\n
\n", "
\nhello\n
", "pre blocks don't include extra lines" ); assert.cooked( "```\na\nb\nc\n\nd\n```", '
a\nb\nc\n\nd\n
', "it treats new lines properly" ); assert.cooked( "```\ntest\n```", '
test\n
', "it supports basic code blocks" ); assert.cooked( "```json\n{hello: 'world'}\n```\ntrailing", "
{hello: 'world'}\n
\n

trailing

", "It does not truncate text after a code block." ); assert.cooked( "```json\nline 1\n\nline 2\n\n\nline3\n```", '
line 1\n\nline 2\n\n\nline3\n
', "it maintains new lines inside a code block." ); assert.cooked( "hello\nworld\n```json\nline 1\n\nline 2\n\n\nline3\n```", '

hello
\nworld

\n
line 1\n\nline 2\n\n\nline3\n
', "it maintains new lines inside a code block with leading content." ); assert.cooked( "```ruby\n
hello
\n```", '
<header>hello</header>\n
', "it escapes code in the code block" ); assert.cooked( "```text\ntext\n```", '
text\n
', "handles text by adding nohighlight" ); assert.cooked( "```ruby\n# cool\n```", '
# cool\n
', "it supports changing the language" ); assert.cooked( " ```\n hello\n ```", "
```\nhello\n```
", "only detect ``` at the beginning of lines" ); assert.cooked( "```ruby\ndef self.parse(text)\n\n text\nend\n```", '
def self.parse(text)\n\n  text\nend\n
', "it allows leading spaces on lines in a code block." ); assert.cooked( "```ruby\nhello `eviltrout`\n```", '
hello `eviltrout`\n
', "it allows code with backticks in it" ); assert.cooked( "```eviltrout\nhello\n```", '
hello\n
', "it doesn't not allowlist all classes" ); assert.cooked( '```\n[quote="sam, post:1, topic:9441, full:true"]This is `` a bug.[/quote]\n```', '
[quote="sam, post:1, topic:9441, full:true"]This is `<not>` a bug.[/quote]\n
', "it allows code with backticks in it" ); assert.cooked( " hello\n
test
", "
hello\n
\n
test
", "it allows an indented code block to by followed by a `
`" ); assert.cooked( "``` foo bar ```", "

foo bar

", "it tolerates misuse of code block tags as inline code" ); assert.cooked( "```\nline1\n```\n```\nline2\n\nline3\n```", '
line1\n
\n
line2\n\nline3\n
', "it does not consume next block's trailing newlines" ); assert.cooked( "
test
", "
<pre>test</pre>
", "it does not parse other block types in markdown code blocks" ); assert.cooked( " [quote]test[/quote]", "
[quote]test[/quote]
", "it does not parse other block types in markdown code blocks" ); assert.cooked( "## a\nb\n```\nc\n```", '

a

\n

b

\n
c\n
', "it handles headings with code blocks after them." ); }); QUnit.test("URLs in BBCode tags", (assert) => { assert.cooked( "[img]http://eviltrout.com/eviltrout.png[/img][img]http://samsaffron.com/samsaffron.png[/img]", '

', "images are properly parsed" ); assert.cooked( "[url]http://discourse.org[/url]", '

http://discourse.org

', "links are properly parsed" ); assert.cooked( "[url=http://discourse.org]discourse[/url]", '

discourse

', "named links are properly parsed" ); }); QUnit.test("images", (assert) => { assert.cooked( "[![folksy logo](http://folksy.com/images/folksy-colour.png)](http://folksy.com/)", '

folksy logo

', "It allows images with links around them" ); assert.cooked( 'Red dot', '

Red dot

', "It allows data images" ); }); QUnit.test("attachment", (assert) => { assert.cooked( "[test.pdf|attachment](upload://o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf)", `

test.pdf

`, "It returns the correct attachment link HTML" ); }); QUnit.test("attachment - mapped url - secure media disabled", (assert) => { function lookupUploadUrls() { let cache = {}; cache["upload://o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf"] = { short_url: "upload://o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf", url: "/secure-media-uploads/original/3X/c/b/o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf", short_path: "/uploads/short-url/blah", }; return cache; } assert.cookedOptions( "[test.pdf|attachment](upload://o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf)", { siteSettings: { secure_media: false }, lookupUploadUrls: lookupUploadUrls, }, `

test.pdf

`, "It returns the correct attachment link HTML when the URL is mapped without secure media" ); }); QUnit.test("attachment - mapped url - secure media enabled", (assert) => { function lookupUploadUrls() { let cache = {}; cache["upload://o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf"] = { short_url: "upload://o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf", url: "/secure-media-uploads/original/3X/c/b/o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf", short_path: "/uploads/short-url/blah", }; return cache; } assert.cookedOptions( "[test.pdf|attachment](upload://o8iobpLcW3WSFvVH7YQmyGlKmGM.pdf)", { siteSettings: { secure_media: true }, lookupUploadUrls: lookupUploadUrls, }, `

test.pdf

`, "It returns the correct attachment link HTML when the URL is mapped with secure media" ); }); QUnit.test("video", (assert) => { assert.cooked( "![baby shark|video](upload://eyPnj7UzkU0AkGkx2dx8G4YM1Jx.mp4)", `

`, "It returns the correct video player HTML" ); }); QUnit.test("video - mapped url - secure media enabled", (assert) => { function lookupUploadUrls() { let cache = {}; cache["upload://eyPnj7UzkU0AkGkx2dx8G4YM1Jx.mp4"] = { short_url: "upload://eyPnj7UzkU0AkGkx2dx8G4YM1Jx.mp4", url: "/secure-media-uploads/original/3X/c/b/test.mp4", short_path: "/uploads/short-url/blah", }; return cache; } assert.cookedOptions( "![baby shark|video](upload://eyPnj7UzkU0AkGkx2dx8G4YM1Jx.mp4)", { siteSettings: { secure_media: true }, lookupUploadUrls: lookupUploadUrls, }, `

`, "It returns the correct video HTML when the URL is mapped with secure media, removing data-orig-src" ); }); QUnit.test("audio", (assert) => { assert.cooked( "![young americans|audio](upload://eyPnj7UzkU0AkGkx2dx8G4YM1Jx.mp3)", `

`, "It returns the correct audio player HTML" ); }); QUnit.test("audio - mapped url - secure media enabled", (assert) => { function lookupUploadUrls() { let cache = {}; cache["upload://eyPnj7UzkU0AkGkx2dx8G4YM1Jx.mp3"] = { short_url: "upload://eyPnj7UzkU0AkGkx2dx8G4YM1Jx.mp3", url: "/secure-media-uploads/original/3X/c/b/test.mp3", short_path: "/uploads/short-url/blah", }; return cache; } assert.cookedOptions( "![baby shark|audio](upload://eyPnj7UzkU0AkGkx2dx8G4YM1Jx.mp3)", { siteSettings: { secure_media: true }, lookupUploadUrls: lookupUploadUrls, }, `

`, "It returns the correct audio HTML when the URL is mapped with secure media, removing data-orig-src" ); }); QUnit.test("censoring", (assert) => { assert.cookedOptions( "Pleased to meet you, but pleeeease call me later, xyz123", { censoredRegexp: "(xyz*|plee+ase)", }, "

Pleased to meet you, but ■■■■■■■■■ call me later, ■■■123

", "supports censoring" ); // More tests in pretty_text_spec.rb }); QUnit.test("code blocks/spans hoisting", (assert) => { assert.cooked( "```\n\n some code\n```", '
\n    some code\n
', "it works when nesting standard markdown code blocks within a fenced code block" ); assert.cooked( "`$&`", "

$&

", "it works even when hoisting special replacement patterns" ); }); QUnit.test("basic bbcode", (assert) => { assert.cookedPara( "[b]strong[/b]", 'strong', "bolds text" ); assert.cookedPara( "[i]emphasis[/i]", 'emphasis', "italics text" ); assert.cookedPara( "[u]underlined[/u]", 'underlined', "underlines text" ); assert.cookedPara( "[s]strikethrough[/s]", 'strikethrough', "strikes-through text" ); assert.cookedPara( "[img]http://eviltrout.com/eviltrout.png[/img]", '', "links images" ); assert.cookedPara( "[email]eviltrout@mailinator.com[/email]", 'eviltrout@mailinator.com', "supports [email] without a title" ); assert.cookedPara( "[b]evil [i]trout[/i][/b]", 'evil trout', "allows embedding of tags" ); assert.cookedPara( "[EMAIL]eviltrout@mailinator.com[/EMAIL]", 'eviltrout@mailinator.com', "supports upper case bbcode" ); assert.cookedPara( "[b]strong [b]stronger[/b][/b]", 'strong stronger', "accepts nested bbcode tags" ); }); QUnit.test("urls", (assert) => { assert.cookedPara( "[url]not a url[/url]", "not a url", "supports [url] that isn't a url" ); assert.cookedPara( "[url]abc.com[/url]", 'abc.com', "it magically links using linkify" ); assert.cookedPara( "[url]http://bettercallsaul.com[/url]", 'http://bettercallsaul.com', "supports [url] without parameter" ); assert.cookedPara( "[url=http://example.com]example[/url]", 'example', "supports [url] with given href" ); assert.cookedPara( "[url=http://www.example.com][img]http://example.com/logo.png[/img][/url]", '', "supports [url] with an embedded [img]" ); }); QUnit.test("invalid bbcode", (assert) => { assert.cooked( "[code]I am not closed\n\nThis text exists.", "

[code]I am not closed

\n

This text exists.

", "does not raise an error with an open bbcode tag." ); }); QUnit.test("code", (assert) => { assert.cooked( "[code]\nx++\n[/code]", '
x++
', "makes code into pre" ); assert.cooked( "[code]\nx++\ny++\nz++\n[/code]", '
x++\ny++\nz++
', "makes code into pre" ); assert.cooked( "[code]\nabc\n#def\n[/code]", '
abc\n#def
', "it handles headings in a [code] block" ); assert.cooked( "[code]\n s\n[/code]", '
   s
', "it doesn't trim leading whitespace" ); }); QUnit.test("tags with arguments", (assert) => { assert.cookedPara( "[url=http://bettercallsaul.com]better call![/url]", 'better call!', "supports [url] with a title" ); assert.cookedPara( "[email=eviltrout@mailinator.com]evil trout[/email]", 'evil trout', "supports [email] with a title" ); assert.cookedPara( "[u][i]abc[/i][/u]", 'abc', "can nest tags" ); assert.cookedPara( "[b]first[/b] [b]second[/b]", 'first second', "can bold two things on the same line" ); }); QUnit.test("quotes", (assert) => { const post = Post.create({ cooked: "

lorem ipsum

", username: "eviltrout", post_number: 1, topic_id: 2, }); function formatQuote(val, expected, text, opts) { assert.equal(buildQuote(post, val, opts), expected, text); } formatQuote(undefined, "", "empty string for undefined content"); formatQuote(null, "", "empty string for null content"); formatQuote("", "", "empty string for empty string content"); formatQuote( "lorem", '[quote="eviltrout, post:1, topic:2"]\nlorem\n[/quote]\n\n', "correctly formats quotes" ); formatQuote( " lorem \t ", '[quote="eviltrout, post:1, topic:2"]\nlorem\n[/quote]\n\n', "trims white spaces before & after the quoted contents" ); formatQuote( "lorem ipsum", '[quote="eviltrout, post:1, topic:2, full:true"]\nlorem ipsum\n[/quote]\n\n', "marks quotes as full if the `full` option is passed", { full: true } ); formatQuote( "**lorem** ipsum", '[quote="eviltrout, post:1, topic:2"]\n**lorem** ipsum\n[/quote]\n\n', "keeps BBCode formatting" ); assert.cooked( "[quote]\ntest\n[/quote]", '', "it supports quotes without params" ); assert.cooked( "[quote]\n*test*\n[/quote]", '', "it doesn't insert a new line for italics" ); assert.cooked( "[quote=,script='a'>