mirror of
https://github.com/discourse/discourse.git
synced 2025-06-24 19:01:33 +08:00
REFACTOR: Migrate markdown functionality in ES6
This commit is contained in:
@ -11,9 +11,6 @@ acceptance("Category hashtag", {
|
||||
];
|
||||
};
|
||||
|
||||
server.get('/category_hashtags/check', () => { //eslint-disable-line
|
||||
return response({ valid: [{ slug: "bug", url: '/c/bugs' }] });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -101,6 +101,10 @@ export default function() {
|
||||
return response({ post_reply_histories: [{ id: 1234, cooked: 'wat' }] });
|
||||
});
|
||||
|
||||
this.get('/category_hashtags/check', () => {
|
||||
return response({ valid: [{ slug: "bug", url: '/c/bugs' }] });
|
||||
});
|
||||
|
||||
this.put('/categories/:category_id', request => {
|
||||
const category = parsePostData(request.requestBody);
|
||||
return response({category});
|
||||
|
@ -22,7 +22,7 @@ export default function() {
|
||||
return (this._tracker);
|
||||
}
|
||||
if (type === "site-settings:main") {
|
||||
this._settings = this._settings || Discourse.SiteSettings.current();
|
||||
this._settings = this._settings || Discourse.SiteSettings;
|
||||
return (this._settings);
|
||||
}
|
||||
},
|
||||
|
@ -33,16 +33,11 @@ function AcceptanceModal(option, _relatedTarget) {
|
||||
window.bootbox.$body = $('#ember-testing');
|
||||
$.fn.modal = AcceptanceModal;
|
||||
|
||||
var oldAvatar = Discourse.Utilities.avatarImg;
|
||||
|
||||
function acceptance(name, options) {
|
||||
module("Acceptance: " + name, {
|
||||
setup() {
|
||||
resetMobile();
|
||||
|
||||
// Don't render avatars in acceptance tests, it's faster and no 404s
|
||||
Discourse.Utilities.avatarImg = () => "";
|
||||
|
||||
// For now don't do scrolling stuff in Test Mode
|
||||
HeaderComponent.reopen({examineDockHeader: Ember.K});
|
||||
|
||||
@ -79,7 +74,6 @@ function acceptance(name, options) {
|
||||
Discourse.User.resetCurrent();
|
||||
Discourse.Site.resetCurrent(Discourse.Site.create(jQuery.extend(true, {}, fixtures['site.json'].site)));
|
||||
|
||||
Discourse.Utilities.avatarImg = oldAvatar;
|
||||
Discourse.reset();
|
||||
}
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ Discourse.SiteSettingsOriginal = {
|
||||
"newuser_max_attachments":0,
|
||||
"display_name_on_posts":true,
|
||||
"short_progress_text_threshold":10000,
|
||||
"default_code_lang":"lang-auto",
|
||||
"default_code_lang":"auto",
|
||||
"autohighlight_all_code":false,
|
||||
"email_in":false,
|
||||
"authorized_extensions":".jpg|.jpeg|.png|.gif|.svg|.txt|.ico|.yml",
|
||||
|
@ -1,156 +0,0 @@
|
||||
import Quote from 'discourse/lib/quote';
|
||||
import Post from 'discourse/models/post';
|
||||
|
||||
module("Discourse.BBCode");
|
||||
|
||||
var format = function(input, expected, text) {
|
||||
var cooked = Discourse.Markdown.cook(input, {lookupAvatar: false, sanitize: true});
|
||||
equal(cooked, "<p>" + expected + "</p>", text);
|
||||
};
|
||||
|
||||
var formatQ = function(input, expected, text) {
|
||||
var cooked = Discourse.Markdown.cook(input, {lookupAvatar: false, sanitize: true});
|
||||
equal(cooked, expected, text);
|
||||
};
|
||||
|
||||
test('basic bbcode', function() {
|
||||
format("[b]strong[/b]", "<span class=\"bbcode-b\">strong</span>", "bolds text");
|
||||
format("[i]emphasis[/i]", "<span class=\"bbcode-i\">emphasis</span>", "italics text");
|
||||
format("[u]underlined[/u]", "<span class=\"bbcode-u\">underlined</span>", "underlines text");
|
||||
format("[s]strikethrough[/s]", "<span class=\"bbcode-s\">strikethrough</span>", "strikes-through text");
|
||||
format("[img]http://eviltrout.com/eviltrout.png[/img]", "<img src=\"http://eviltrout.com/eviltrout.png\">", "links images");
|
||||
format("[email]eviltrout@mailinator.com[/email]", "<a href=\"mailto:eviltrout@mailinator.com\">eviltrout@mailinator.com</a>", "supports [email] without a title");
|
||||
format("[b]evil [i]trout[/i][/b]",
|
||||
"<span class=\"bbcode-b\">evil <span class=\"bbcode-i\">trout</span></span>",
|
||||
"allows embedding of tags");
|
||||
format("[EMAIL]eviltrout@mailinator.com[/EMAIL]", "<a href=\"mailto:eviltrout@mailinator.com\">eviltrout@mailinator.com</a>", "supports upper case bbcode");
|
||||
format("[b]strong [b]stronger[/b][/b]", "<span class=\"bbcode-b\">strong <span class=\"bbcode-b\">stronger</span></span>", "accepts nested bbcode tags");
|
||||
});
|
||||
|
||||
test('urls', function() {
|
||||
format("[url]not a url[/url]", "not a url", "supports [url] that isn't a url");
|
||||
format("[url]http://bettercallsaul.com[/url]", "<a href=\"http://bettercallsaul.com\">http://bettercallsaul.com</a>", "supports [url] without parameter");
|
||||
format("[url=http://example.com]example[/url]", "<a href=\"http://example.com\">example</a>", "supports [url] with given href");
|
||||
format("[url=http://www.example.com][img]http://example.com/logo.png[/img][/url]",
|
||||
"<a href=\"http://www.example.com\"><img src=\"http://example.com/logo.png\"></a>",
|
||||
"supports [url] with an embedded [img]");
|
||||
});
|
||||
test('invalid bbcode', function() {
|
||||
var cooked = Discourse.Markdown.cook("[code]I am not closed\n\nThis text exists.", {lookupAvatar: false});
|
||||
equal(cooked, "<p>[code]I am not closed</p>\n\n<p>This text exists.</p>", "does not raise an error with an open bbcode tag.");
|
||||
});
|
||||
|
||||
test('code', function() {
|
||||
format("[code]\nx++\n[/code]", "<pre><code class=\"lang-auto\">x++</code></pre>", "makes code into pre");
|
||||
format("[code]\nx++\ny++\nz++\n[/code]", "<pre><code class=\"lang-auto\">x++\ny++\nz++</code></pre>", "makes code into pre");
|
||||
format("[code]abc\n#def\n[/code]", '<pre><code class=\"lang-auto\">abc\n#def</code></pre>', 'it handles headings in a [code] block');
|
||||
format("[code]\n s[/code]",
|
||||
"<pre><code class=\"lang-auto\"> s</code></pre>",
|
||||
"it doesn't trim leading whitespace");
|
||||
});
|
||||
|
||||
test('lists', function() {
|
||||
format("[ul][li]option one[/li][/ul]", "<ul><li>option one</li></ul>", "creates an ul");
|
||||
format("[ol][li]option one[/li][/ol]", "<ol><li>option one</li></ol>", "creates an ol");
|
||||
format("[ul]\n[li]option one[/li]\n[li]option two[/li]\n[/ul]", "<ul><li>option one</li><li>option two</li></ul>", "suppresses empty lines in lists");
|
||||
});
|
||||
|
||||
test('tags with arguments', function() {
|
||||
format("[url=http://bettercallsaul.com]better call![/url]", "<a href=\"http://bettercallsaul.com\">better call!</a>", "supports [url] with a title");
|
||||
format("[email=eviltrout@mailinator.com]evil trout[/email]", "<a href=\"mailto:eviltrout@mailinator.com\">evil trout</a>", "supports [email] with a title");
|
||||
format("[u][i]abc[/i][/u]", "<span class=\"bbcode-u\"><span class=\"bbcode-i\">abc</span></span>", "can nest tags");
|
||||
format("[b]first[/b] [b]second[/b]", "<span class=\"bbcode-b\">first</span> <span class=\"bbcode-b\">second</span>", "can bold two things on the same line");
|
||||
});
|
||||
|
||||
|
||||
test("quotes", function() {
|
||||
|
||||
var post = Post.create({
|
||||
cooked: "<p><b>lorem</b> ipsum</p>",
|
||||
username: "eviltrout",
|
||||
post_number: 1,
|
||||
topic_id: 2
|
||||
});
|
||||
|
||||
var formatQuote = function(val, expected, text) {
|
||||
equal(Quote.build(post, val), 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 when the quote is the full message");
|
||||
|
||||
formatQuote("**lorem** ipsum",
|
||||
"[quote=\"eviltrout, post:1, topic:2, full:true\"]\n**lorem** ipsum\n[/quote]\n\n",
|
||||
"keeps BBCode formatting");
|
||||
|
||||
formatQuote("this is <not> a bug",
|
||||
"[quote=\"eviltrout, post:1, topic:2\"]\nthis is <not> a bug\n[/quote]\n\n",
|
||||
"it escapes the contents of the quote");
|
||||
|
||||
format("[quote]test[/quote]",
|
||||
"<aside class=\"quote\"><blockquote><p>test</p></blockquote></aside>",
|
||||
"it supports quotes without params");
|
||||
|
||||
format("[quote]\n*test*\n[/quote]",
|
||||
"<aside class=\"quote\"><blockquote><p><em>test</em></p></blockquote></aside>",
|
||||
"it doesn't insert a new line for italics");
|
||||
|
||||
format("[quote=,script='a'><script>alert('test');//':a][/quote]",
|
||||
"<aside class=\"quote\"><blockquote></blockquote></aside>",
|
||||
"It will not create a script tag within an attribute");
|
||||
});
|
||||
|
||||
test("quote formatting", function() {
|
||||
|
||||
formatQ("[quote=\"EvilTrout, post:123, topic:456, full:true\"][sam][/quote]",
|
||||
"<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">" +
|
||||
"<div class=\"quote-controls\"></div>EvilTrout:</div><blockquote><p>[sam]</p></blockquote></aside>",
|
||||
"it allows quotes with [] inside");
|
||||
|
||||
formatQ("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>eviltrout:" +
|
||||
"</div><blockquote><p>abc</p></blockquote></aside>",
|
||||
"renders quotes properly");
|
||||
|
||||
formatQ("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]\nhello",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>eviltrout:" +
|
||||
"</div><blockquote><p>abc</p></blockquote></aside>\n\n<p>hello</p>",
|
||||
"handles new lines properly");
|
||||
|
||||
formatQ("[quote=\"Alice, post:1, topic:1\"]\n[quote=\"Bob, post:2, topic:1\"]\n[/quote]\n[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Alice:" +
|
||||
"</div><blockquote><aside class=\"quote\" data-post=\"2\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Bob:" +
|
||||
"</div><blockquote></blockquote></aside></blockquote></aside>",
|
||||
"quotes can be nested");
|
||||
|
||||
formatQ("[quote=\"Alice, post:1, topic:1\"]\n[quote=\"Bob, post:2, topic:1\"]\n[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Alice:" +
|
||||
"</div><blockquote><p>[quote=\"Bob, post:2, topic:1\"]</p></blockquote></aside>",
|
||||
"handles mismatched nested quote tags");
|
||||
|
||||
formatQ("[quote=\"Alice, post:1, topic:1\"]\n```javascript\nvar foo ='foo';\nvar bar = 'bar';\n```\n[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Alice:</div><blockquote><p><pre><code class=\"lang-javascript\">var foo ='foo';\nvar bar = 'bar';</code></pre></p></blockquote></aside>",
|
||||
"quotes can have code blocks without leading newline");
|
||||
formatQ("[quote=\"Alice, post:1, topic:1\"]\n\n```javascript\nvar foo ='foo';\nvar bar = 'bar';\n```\n[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Alice:</div><blockquote><p><pre><code class=\"lang-javascript\">var foo ='foo';\nvar bar = 'bar';</code></pre></p></blockquote></aside>",
|
||||
"quotes can have code blocks with leading newline");
|
||||
});
|
||||
|
||||
test("quotes with trailing formatting", function() {
|
||||
var cooked = Discourse.Markdown.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]\nhello\n[/quote]\n*Test*", {lookupAvatar: false});
|
||||
equal(cooked,
|
||||
"<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">" +
|
||||
"<div class=\"quote-controls\"></div>EvilTrout:</div><blockquote><p>hello</p></blockquote></aside>\n\n<p><em>Test</em></p>",
|
||||
"it allows trailing formatting");
|
||||
});
|
@ -1,15 +1,13 @@
|
||||
import { emojiSearch, IMAGE_VERSION as v } from 'pretty-text/emoji';
|
||||
import { emojiUnescape } from 'discourse/lib/text';
|
||||
|
||||
module('emoji');
|
||||
module('lib:emoji');
|
||||
|
||||
var testUnescape = function(input, expected, description) {
|
||||
var unescaped = Discourse.Emoji.unescape(input);
|
||||
equal(unescaped, expected, description);
|
||||
function testUnescape(input, expected, description) {
|
||||
equal(emojiUnescape(input), expected, description);
|
||||
};
|
||||
|
||||
test("Emoji.unescape", function(){
|
||||
|
||||
const v = Discourse.Emoji.ImageVersion;
|
||||
|
||||
test("emojiUnescape", () => {
|
||||
testUnescape("Not emoji :O) :frog) :smile)", "Not emoji :O) :frog) :smile)", "title without emoji");
|
||||
testUnescape("Not emoji :frog :smile", "Not emoji :frog :smile", "end colon is not optional");
|
||||
testUnescape("emoticons :)", "emoticons <img src='/images/emoji/emoji_one/slight_smile.png?v=2' title='slight_smile' alt='slight_smile' class='emoji'>", "emoticons are still supported");
|
||||
@ -23,12 +21,12 @@ test("Emoji.unescape", function(){
|
||||
|
||||
});
|
||||
|
||||
test("Emoji.search", function(){
|
||||
test("Emoji search", () => {
|
||||
|
||||
// able to find an alias
|
||||
equal(Discourse.Emoji.search("+1").length, 1);
|
||||
equal(emojiSearch("+1").length, 1);
|
||||
|
||||
// able to find middle of line search
|
||||
equal(Discourse.Emoji.search("check", {maxResults: 3}).length, 3);
|
||||
equal(emojiSearch("check", {maxResults: 3}).length, 3);
|
||||
|
||||
});
|
||||
|
@ -1,23 +0,0 @@
|
||||
module("Discourse.Onebox", {
|
||||
setup: function() {
|
||||
this.anchor = $("<a href='http://bla.com'></a>")[0];
|
||||
}
|
||||
});
|
||||
|
||||
asyncTestDiscourse("Stops rapid calls with cache true", function() {
|
||||
sandbox.stub(Discourse, "ajax").returns(Ember.RSVP.resolve());
|
||||
Discourse.Onebox.load(this.anchor, true);
|
||||
Discourse.Onebox.load(this.anchor, true);
|
||||
|
||||
start();
|
||||
ok(Discourse.ajax.calledOnce);
|
||||
});
|
||||
|
||||
asyncTestDiscourse("Stops rapid calls with cache true", function() {
|
||||
sandbox.stub(Discourse, "ajax").returns(Ember.RSVP.resolve());
|
||||
Discourse.Onebox.load(this.anchor, false);
|
||||
Discourse.Onebox.load(this.anchor, false);
|
||||
|
||||
start();
|
||||
ok(Discourse.ajax.calledOnce);
|
||||
});
|
@ -1,21 +1,41 @@
|
||||
module("Discourse.Markdown", {
|
||||
setup: function() {
|
||||
Discourse.SiteSettings.traditional_markdown_linebreaks = false;
|
||||
Discourse.SiteSettings.default_code_lang = "auto";
|
||||
}
|
||||
import Quote from 'discourse/lib/quote';
|
||||
import Post from 'discourse/models/post';
|
||||
import { default as PrettyText, buildOptions } from 'pretty-text/pretty-text';
|
||||
import { IMAGE_VERSION as v} from 'pretty-text/emoji';
|
||||
|
||||
module("lib:pretty-text");
|
||||
|
||||
const defaultOpts = buildOptions({
|
||||
siteSettings: {
|
||||
enable_emoji: true,
|
||||
emoji_set: 'emoji_one',
|
||||
highlighted_languages: 'json|ruby|javascript',
|
||||
default_code_lang: 'auto',
|
||||
censored_words: 'shucks|whiz|whizzer'
|
||||
},
|
||||
getURL: url => url
|
||||
});
|
||||
|
||||
var cooked = function(input, expected, text) {
|
||||
var result = Discourse.Markdown.cook(input, {sanitize: true});
|
||||
expected = expected.replace(/\/>/g, ">");
|
||||
// result = result.replace("/>", ">");
|
||||
equal(result, expected, text);
|
||||
function cooked(input, expected, text) {
|
||||
equal(new PrettyText(defaultOpts).cook(input), expected.replace(/\/>/g, ">"), text);
|
||||
};
|
||||
|
||||
var cookedOptions = function(input, opts, expected, text) {
|
||||
equal(Discourse.Markdown.cook(input, opts), expected, text);
|
||||
function cookedOptions(input, opts, expected, text) {
|
||||
equal(new PrettyText(_.merge({}, defaultOpts, opts)).cook(input), expected, text);
|
||||
};
|
||||
|
||||
function cookedPara(input, expected, text) {
|
||||
cooked(input, `<p>${expected}</p>`, text);
|
||||
};
|
||||
|
||||
test("buildOptions", () => {
|
||||
ok(buildOptions({ siteSettings: { allow_html_tables: true } }).features.table, 'tables enabled');
|
||||
ok(!buildOptions({ siteSettings: { allow_html_tables: false } }).features.table, 'tables disabled');
|
||||
|
||||
ok(buildOptions({ siteSettings: { enable_emoji: true } }).features.emoji, 'emoji enabled');
|
||||
ok(!buildOptions({ siteSettings: { enable_emoji: false } }).features.emoji, 'emoji disabled');
|
||||
});
|
||||
|
||||
test("basic cooking", function() {
|
||||
cooked("hello", "<p>hello</p>", "surrounds text with paragraphs");
|
||||
cooked("**evil**", "<p><strong>evil</strong></p>", "it bolds text.");
|
||||
@ -35,18 +55,11 @@ test("Nested bold and italics", function() {
|
||||
});
|
||||
|
||||
test("Traditional Line Breaks", function() {
|
||||
var input = "1\n2\n3";
|
||||
const input = "1\n2\n3";
|
||||
cooked(input, "<p>1<br/>2<br/>3</p>", "automatically handles trivial newlines");
|
||||
|
||||
var traditionalOutput = "<p>1\n2\n3</p>";
|
||||
|
||||
cookedOptions(input,
|
||||
{traditional_markdown_linebreaks: true},
|
||||
traditionalOutput,
|
||||
"It supports traditional markdown via an option");
|
||||
|
||||
Discourse.SiteSettings.traditional_markdown_linebreaks = true;
|
||||
cooked(input, traditionalOutput, "It supports traditional markdown via a Site Setting");
|
||||
const result = new PrettyText({ traditionalMarkdownLinebreaks: true }).cook(input);
|
||||
equal(result, "<p>1\n2\n3</p>");
|
||||
});
|
||||
|
||||
test("Unbalanced underscores", function() {
|
||||
@ -72,7 +85,6 @@ test("Paragraphs for HTML", function() {
|
||||
cooked("<p>hello world</p>", "<p>hello world</p>", "it doesn't surround <p> with paragraphs");
|
||||
cooked("<i>hello world</i>", "<p><i>hello world</i></p>", "it surrounds inline <i> html tags with paragraphs");
|
||||
cooked("<b>hello world</b>", "<p><b>hello world</b></p>", "it surrounds inline <b> html tags with paragraphs");
|
||||
|
||||
});
|
||||
|
||||
test("Links", function() {
|
||||
@ -145,7 +157,7 @@ test("Links", function() {
|
||||
|
||||
cooked("[http://google.com ... wat](http://discourse.org)",
|
||||
"<p><a href=\"http://discourse.org\">http://google.com ... wat</a></p>",
|
||||
"it supports linkins within links");
|
||||
"it supports links within links");
|
||||
|
||||
cooked("[Link](http://www.example.com) (with an outer \"description\")",
|
||||
"<p><a href=\"http://www.example.com\">Link</a> (with an outer \"description\")</p>",
|
||||
@ -202,7 +214,7 @@ test("Quotes", function() {
|
||||
|
||||
test("Mentions", function() {
|
||||
|
||||
var alwaysTrue = { mentionLookup: (function() { return "user"; }) };
|
||||
const alwaysTrue = { mentionLookup: (function() { return "user"; }) };
|
||||
|
||||
cookedOptions("Hello @sam", alwaysTrue,
|
||||
"<p>Hello <a class=\"mention\" href=\"/users/sam\">@sam</a></p>",
|
||||
@ -290,7 +302,7 @@ test("Mentions", function() {
|
||||
});
|
||||
|
||||
test("Category hashtags", () => {
|
||||
var alwaysTrue = { categoryHashtagLookup: (function() { return ["http://test.discourse.org/category-hashtag", "category-hashtag"]; }) };
|
||||
const alwaysTrue = { categoryHashtagLookup: (function() { return ["http://test.discourse.org/category-hashtag", "category-hashtag"]; }) };
|
||||
|
||||
cookedOptions("Check out #category-hashtag", alwaysTrue,
|
||||
"<p>Check out <a class=\"hashtag\" href=\"http://test.discourse.org/category-hashtag\">#<span>category-hashtag</span></a></p>",
|
||||
@ -360,8 +372,8 @@ test("New Lines", function() {
|
||||
|
||||
test("Oneboxing", function() {
|
||||
|
||||
var matches = function(input, regexp) {
|
||||
return Discourse.Markdown.cook(input).match(regexp);
|
||||
function matches(input, regexp) {
|
||||
return new PrettyText(defaultOpts).cook(input).match(regexp);
|
||||
};
|
||||
|
||||
ok(!matches("- http://www.textfiles.com/bbs/MINDVOX/FORUMS/ethics\n\n- http://drupal.org", /onebox/),
|
||||
@ -472,49 +484,6 @@ test("Code Blocks", function() {
|
||||
"it handles headings with code blocks after them.");
|
||||
});
|
||||
|
||||
test("sanitize", function() {
|
||||
var sanitize = Discourse.Markdown.sanitize;
|
||||
|
||||
equal(sanitize("<i class=\"fa-bug fa-spin\">bug</i>"), "<i>bug</i>");
|
||||
equal(sanitize("<div><script>alert('hi');</script></div>"), "<div></div>");
|
||||
equal(sanitize("<div><p class=\"funky\" wrong='1'>hello</p></div>"), "<div><p>hello</p></div>");
|
||||
equal(sanitize("<3 <3"), "<3 <3");
|
||||
equal(sanitize("<_<"), "<_<");
|
||||
cooked("hello<script>alert(42)</script>", "<p>hello</p>", "it sanitizes while cooking");
|
||||
|
||||
cooked("<a href='http://disneyland.disney.go.com/'>disney</a> <a href='http://reddit.com'>reddit</a>",
|
||||
"<p><a href=\"http://disneyland.disney.go.com/\">disney</a> <a href=\"http://reddit.com\">reddit</a></p>",
|
||||
"we can embed proper links");
|
||||
|
||||
cooked("<center>hello</center>", "<p>hello</p>", "it does not allow centering");
|
||||
cooked("<table><tr><td>hello</td></tr></table>\nafter", "<p>after</p>", "it does not allow tables");
|
||||
cooked("<blockquote>a\n</blockquote>\n", "<blockquote>a\n\n<br/>\n\n</blockquote>", "it does not double sanitize");
|
||||
|
||||
cooked("<iframe src=\"http://discourse.org\" width=\"100\" height=\"42\"></iframe>", "", "it does not allow most iframe");
|
||||
|
||||
cooked("<iframe src=\"https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d2624.9983685732213!2d2.29432085!3d48.85824149999999!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2s!4v1385737436368\" width=\"100\" height=\"42\"></iframe>",
|
||||
"<iframe src=\"https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d2624.9983685732213!2d2.29432085!3d48.85824149999999!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2s!4v1385737436368\" width=\"100\" height=\"42\"></iframe>",
|
||||
"it allows iframe to google maps");
|
||||
|
||||
cooked("<iframe width=\"425\" height=\"350\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" src=\"http://www.openstreetmap.org/export/embed.html?bbox=22.49454975128174%2C51.220338322410775%2C22.523088455200195%2C51.23345342732931&layer=mapnik\"></iframe>",
|
||||
"<iframe width=\"425\" height=\"350\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" src=\"http://www.openstreetmap.org/export/embed.html?bbox=22.49454975128174%2C51.220338322410775%2C22.523088455200195%2C51.23345342732931&layer=mapnik\"></iframe>",
|
||||
"it allows iframe to OpenStreetMap");
|
||||
|
||||
equal(sanitize("<textarea>hullo</textarea>"), "hullo");
|
||||
equal(sanitize("<button>press me!</button>"), "press me!");
|
||||
equal(sanitize("<canvas>draw me!</canvas>"), "draw me!");
|
||||
equal(sanitize("<progress>hello"), "hello");
|
||||
equal(sanitize("<mark>highlight</mark>"), "highlight");
|
||||
|
||||
cooked("[the answer](javascript:alert(42))", "<p><a>the answer</a></p>", "it prevents XSS");
|
||||
|
||||
cooked("<i class=\"fa fa-bug fa-spin\" style=\"font-size:600%\"></i>\n<!-- -->", "<p><i></i><br/></p>", "it doesn't circumvent XSS with comments");
|
||||
|
||||
cooked("<span class=\"-bbcode-s fa fa-spin\">a</span>", "<p><span>a</span></p>", "it sanitizes spans");
|
||||
cooked("<span class=\"fa fa-spin -bbcode-s\">a</span>", "<p><span>a</span></p>", "it sanitizes spans");
|
||||
cooked("<span class=\"bbcode-s\">a</span>", "<p><span class=\"bbcode-s\">a</span></p>", "it sanitizes spans");
|
||||
});
|
||||
|
||||
test("URLs in BBCode tags", function() {
|
||||
|
||||
cooked("[img]http://eviltrout.com/eviltrout.png[/img][img]http://samsaffron.com/samsaffron.png[/img]",
|
||||
@ -531,23 +500,6 @@ test("URLs in BBCode tags", function() {
|
||||
|
||||
});
|
||||
|
||||
test("urlAllowed", function() {
|
||||
var urlAllowed = Discourse.Markdown.urlAllowed;
|
||||
|
||||
var allowed = function(url, msg) {
|
||||
equal(urlAllowed(url), url, msg);
|
||||
};
|
||||
|
||||
allowed("/foo/bar.html", "allows relative urls");
|
||||
allowed("http://eviltrout.com/evil/trout", "allows full urls");
|
||||
allowed("https://eviltrout.com/evil/trout", "allows https urls");
|
||||
allowed("//eviltrout.com/evil/trout", "allows protocol relative urls");
|
||||
|
||||
equal(urlAllowed("http://google.com/test'onmouseover=alert('XSS!');//.swf"),
|
||||
"http://google.com/test%27onmouseover=alert(%27XSS!%27);//.swf",
|
||||
"escape single quotes");
|
||||
});
|
||||
|
||||
test("images", function() {
|
||||
cooked("[](http://folksy.com/)",
|
||||
"<p><a href=\"http://folksy.com/\"><img src=\"http://folksy.com/images/folksy-colour.png\" alt=\"folksy logo\"/></a></p>",
|
||||
@ -559,7 +511,6 @@ test("images", function() {
|
||||
});
|
||||
|
||||
test("censoring", function() {
|
||||
Discourse.SiteSettings.censored_words = "shucks|whiz|whizzer";
|
||||
cooked("aw shucks, golly gee whiz.",
|
||||
"<p>aw ■■■■■■, golly gee ■■■■.</p>",
|
||||
"it censors words in the Site Settings");
|
||||
@ -583,3 +534,165 @@ test("code blocks/spans hoisting", function() {
|
||||
"<p><code>$&</code></p>",
|
||||
"it works even when hoisting special replacement patterns");
|
||||
});
|
||||
|
||||
test('basic bbcode', function() {
|
||||
cookedPara("[b]strong[/b]", "<span class=\"bbcode-b\">strong</span>", "bolds text");
|
||||
cookedPara("[i]emphasis[/i]", "<span class=\"bbcode-i\">emphasis</span>", "italics text");
|
||||
cookedPara("[u]underlined[/u]", "<span class=\"bbcode-u\">underlined</span>", "underlines text");
|
||||
cookedPara("[s]strikethrough[/s]", "<span class=\"bbcode-s\">strikethrough</span>", "strikes-through text");
|
||||
cookedPara("[img]http://eviltrout.com/eviltrout.png[/img]", "<img src=\"http://eviltrout.com/eviltrout.png\">", "links images");
|
||||
cookedPara("[email]eviltrout@mailinator.com[/email]", "<a href=\"mailto:eviltrout@mailinator.com\">eviltrout@mailinator.com</a>", "supports [email] without a title");
|
||||
cookedPara("[b]evil [i]trout[/i][/b]",
|
||||
"<span class=\"bbcode-b\">evil <span class=\"bbcode-i\">trout</span></span>",
|
||||
"allows embedding of tags");
|
||||
cookedPara("[EMAIL]eviltrout@mailinator.com[/EMAIL]", "<a href=\"mailto:eviltrout@mailinator.com\">eviltrout@mailinator.com</a>", "supports upper case bbcode");
|
||||
cookedPara("[b]strong [b]stronger[/b][/b]", "<span class=\"bbcode-b\">strong <span class=\"bbcode-b\">stronger</span></span>", "accepts nested bbcode tags");
|
||||
});
|
||||
|
||||
test('urls', function() {
|
||||
cookedPara("[url]not a url[/url]", "not a url", "supports [url] that isn't a url");
|
||||
cookedPara("[url]http://bettercallsaul.com[/url]", "<a href=\"http://bettercallsaul.com\">http://bettercallsaul.com</a>", "supports [url] without parameter");
|
||||
cookedPara("[url=http://example.com]example[/url]", "<a href=\"http://example.com\">example</a>", "supports [url] with given href");
|
||||
cookedPara("[url=http://www.example.com][img]http://example.com/logo.png[/img][/url]",
|
||||
"<a href=\"http://www.example.com\"><img src=\"http://example.com/logo.png\"></a>",
|
||||
"supports [url] with an embedded [img]");
|
||||
});
|
||||
test('invalid bbcode', function() {
|
||||
const result = new PrettyText({ lookupAvatar: false }).cook("[code]I am not closed\n\nThis text exists.");
|
||||
equal(result, "<p>[code]I am not closed</p>\n\n<p>This text exists.</p>", "does not raise an error with an open bbcode tag.");
|
||||
});
|
||||
|
||||
test('code', function() {
|
||||
cookedPara("[code]\nx++\n[/code]", "<pre><code class=\"lang-auto\">x++</code></pre>", "makes code into pre");
|
||||
cookedPara("[code]\nx++\ny++\nz++\n[/code]", "<pre><code class=\"lang-auto\">x++\ny++\nz++</code></pre>", "makes code into pre");
|
||||
cookedPara("[code]abc\n#def\n[/code]", '<pre><code class=\"lang-auto\">abc\n#def</code></pre>', 'it handles headings in a [code] block');
|
||||
cookedPara("[code]\n s[/code]",
|
||||
"<pre><code class=\"lang-auto\"> s</code></pre>",
|
||||
"it doesn't trim leading whitespace");
|
||||
});
|
||||
|
||||
test('lists', function() {
|
||||
cookedPara("[ul][li]option one[/li][/ul]", "<ul><li>option one</li></ul>", "creates an ul");
|
||||
cookedPara("[ol][li]option one[/li][/ol]", "<ol><li>option one</li></ol>", "creates an ol");
|
||||
cookedPara("[ul]\n[li]option one[/li]\n[li]option two[/li]\n[/ul]", "<ul><li>option one</li><li>option two</li></ul>", "suppresses empty lines in lists");
|
||||
});
|
||||
|
||||
test('tags with arguments', function() {
|
||||
cookedPara("[url=http://bettercallsaul.com]better call![/url]", "<a href=\"http://bettercallsaul.com\">better call!</a>", "supports [url] with a title");
|
||||
cookedPara("[email=eviltrout@mailinator.com]evil trout[/email]", "<a href=\"mailto:eviltrout@mailinator.com\">evil trout</a>", "supports [email] with a title");
|
||||
cookedPara("[u][i]abc[/i][/u]", "<span class=\"bbcode-u\"><span class=\"bbcode-i\">abc</span></span>", "can nest tags");
|
||||
cookedPara("[b]first[/b] [b]second[/b]", "<span class=\"bbcode-b\">first</span> <span class=\"bbcode-b\">second</span>", "can bold two things on the same line");
|
||||
});
|
||||
|
||||
|
||||
test("quotes", function() {
|
||||
const post = Post.create({
|
||||
cooked: "<p><b>lorem</b> ipsum</p>",
|
||||
username: "eviltrout",
|
||||
post_number: 1,
|
||||
topic_id: 2
|
||||
});
|
||||
|
||||
function formatQuote(val, expected, text) {
|
||||
equal(Quote.build(post, val), 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 when the quote is the full message");
|
||||
|
||||
formatQuote("**lorem** ipsum",
|
||||
"[quote=\"eviltrout, post:1, topic:2, full:true\"]\n**lorem** ipsum\n[/quote]\n\n",
|
||||
"keeps BBCode formatting");
|
||||
|
||||
formatQuote("this is <not> a bug",
|
||||
"[quote=\"eviltrout, post:1, topic:2\"]\nthis is <not> a bug\n[/quote]\n\n",
|
||||
"it escapes the contents of the quote");
|
||||
|
||||
cookedPara("[quote]test[/quote]",
|
||||
"<aside class=\"quote\"><blockquote><p>test</p></blockquote></aside>",
|
||||
"it supports quotes without params");
|
||||
|
||||
cookedPara("[quote]\n*test*\n[/quote]",
|
||||
"<aside class=\"quote\"><blockquote><p><em>test</em></p></blockquote></aside>",
|
||||
"it doesn't insert a new line for italics");
|
||||
|
||||
cookedPara("[quote=,script='a'><script>alert('test');//':a][/quote]",
|
||||
"<aside class=\"quote\"><blockquote></blockquote></aside>",
|
||||
"It will not create a script tag within an attribute");
|
||||
});
|
||||
|
||||
test("quote formatting", function() {
|
||||
|
||||
cooked("[quote=\"EvilTrout, post:123, topic:456, full:true\"][sam][/quote]",
|
||||
"<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">" +
|
||||
"<div class=\"quote-controls\"></div>EvilTrout:</div><blockquote><p>[sam]</p></blockquote></aside>",
|
||||
"it allows quotes with [] inside");
|
||||
|
||||
cooked("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>eviltrout:" +
|
||||
"</div><blockquote><p>abc</p></blockquote></aside>",
|
||||
"renders quotes properly");
|
||||
|
||||
cooked("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]\nhello",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>eviltrout:" +
|
||||
"</div><blockquote><p>abc</p></blockquote></aside>\n\n<p>hello</p>",
|
||||
"handles new lines properly");
|
||||
|
||||
cooked("[quote=\"Alice, post:1, topic:1\"]\n[quote=\"Bob, post:2, topic:1\"]\n[/quote]\n[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Alice:" +
|
||||
"</div><blockquote><aside class=\"quote\" data-post=\"2\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Bob:" +
|
||||
"</div><blockquote></blockquote></aside></blockquote></aside>",
|
||||
"quotes can be nested");
|
||||
|
||||
cooked("[quote=\"Alice, post:1, topic:1\"]\n[quote=\"Bob, post:2, topic:1\"]\n[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Alice:" +
|
||||
"</div><blockquote><p>[quote=\"Bob, post:2, topic:1\"]</p></blockquote></aside>",
|
||||
"handles mismatched nested quote tags");
|
||||
|
||||
cooked("[quote=\"Alice, post:1, topic:1\"]\n```javascript\nvar foo ='foo';\nvar bar = 'bar';\n```\n[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Alice:</div><blockquote><p><pre><code class=\"lang-javascript\">var foo ='foo';\nvar bar = 'bar';</code></pre></p></blockquote></aside>",
|
||||
"quotes can have code blocks without leading newline");
|
||||
cooked("[quote=\"Alice, post:1, topic:1\"]\n\n```javascript\nvar foo ='foo';\nvar bar = 'bar';\n```\n[/quote]",
|
||||
"<aside class=\"quote\" data-post=\"1\" data-topic=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>Alice:</div><blockquote><p><pre><code class=\"lang-javascript\">var foo ='foo';\nvar bar = 'bar';</code></pre></p></blockquote></aside>",
|
||||
"quotes can have code blocks with leading newline");
|
||||
});
|
||||
|
||||
test("quotes with trailing formatting", function() {
|
||||
const result = new PrettyText(defaultOpts).cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]\nhello\n[/quote]\n*Test*");
|
||||
equal(result,
|
||||
"<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">" +
|
||||
"<div class=\"quote-controls\"></div>EvilTrout:</div><blockquote><p>hello</p></blockquote></aside>\n\n<p><em>Test</em></p>",
|
||||
"it allows trailing formatting");
|
||||
});
|
||||
|
||||
test("enable/disable features", () => {
|
||||
const table = `<table><tr><th>hello</th></tr><tr><td>world</td></tr></table>`;
|
||||
const hasTable = new PrettyText({ features: {table: true}, sanitize: true}).cook(table);
|
||||
equal(hasTable, `<table class="md-table"><tr><th>hello</th></tr><tr><td>world</td></tr></table>`);
|
||||
|
||||
const noTable = new PrettyText({ features: { table: false }, sanitize: true}).cook(table);
|
||||
equal(noTable, `<p></p>`, 'tables are stripped when disabled');
|
||||
});
|
||||
|
||||
test("emoji", () => {
|
||||
cooked(":smile:", `<p><img src="/images/emoji/emoji_one/smile.png?v=${v}" title=":smile:" class="emoji" alt=":smile:"></p>`);
|
||||
cooked(":(", `<p><img src="/images/emoji/emoji_one/frowning.png?v=${v}" title=":frowning:" class="emoji" alt=":frowning:"></p>`);
|
||||
cooked("8-)", `<p><img src="/images/emoji/emoji_one/sunglasses.png?v=${v}" title=":sunglasses:" class="emoji" alt=":sunglasses:"></p>`);
|
||||
});
|
||||
|
||||
test("emoji - emojiSet", () => {
|
||||
cookedOptions(":smile:",
|
||||
{ emojiSet: 'twitter' },
|
||||
`<p><img src="/images/emoji/twitter/smile.png?v=${v}" title=":smile:" class="emoji" alt=":smile:"></p>`);
|
||||
});
|
62
test/javascripts/lib/sanitizer-test.js.es6
Normal file
62
test/javascripts/lib/sanitizer-test.js.es6
Normal file
@ -0,0 +1,62 @@
|
||||
import { default as PrettyText, buildOptions } from 'pretty-text/pretty-text';
|
||||
import { hrefAllowed } from 'pretty-text/sanitizer';
|
||||
|
||||
module("lib:sanitizer");
|
||||
|
||||
test("sanitize", function() {
|
||||
const pt = new PrettyText(buildOptions({ siteSettings: {} }));
|
||||
const cooked = (input, expected, text) => equal(pt.cook(input), expected.replace(/\/>/g, ">"), text);
|
||||
|
||||
equal(pt.sanitize("<i class=\"fa-bug fa-spin\">bug</i>"), "<i>bug</i>");
|
||||
equal(pt.sanitize("<div><script>alert('hi');</script></div>"), "<div></div>");
|
||||
equal(pt.sanitize("<div><p class=\"funky\" wrong='1'>hello</p></div>"), "<div><p>hello</p></div>");
|
||||
equal(pt.sanitize("<3 <3"), "<3 <3");
|
||||
equal(pt.sanitize("<_<"), "<_<");
|
||||
cooked("hello<script>alert(42)</script>", "<p>hello</p>", "it sanitizes while cooking");
|
||||
|
||||
cooked("<a href='http://disneyland.disney.go.com/'>disney</a> <a href='http://reddit.com'>reddit</a>",
|
||||
"<p><a href=\"http://disneyland.disney.go.com/\">disney</a> <a href=\"http://reddit.com\">reddit</a></p>",
|
||||
"we can embed proper links");
|
||||
|
||||
cooked("<center>hello</center>", "<p>hello</p>", "it does not allow centering");
|
||||
cooked("<table><tr><td>hello</td></tr></table>\nafter", "<p>after</p>", "it does not allow tables");
|
||||
cooked("<blockquote>a\n</blockquote>\n", "<blockquote>a\n\n<br/>\n\n</blockquote>", "it does not double sanitize");
|
||||
|
||||
cooked("<iframe src=\"http://discourse.org\" width=\"100\" height=\"42\"></iframe>", "", "it does not allow most iframes");
|
||||
|
||||
cooked("<iframe src=\"https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d2624.9983685732213!2d2.29432085!3d48.85824149999999!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2s!4v1385737436368\" width=\"100\" height=\"42\"></iframe>",
|
||||
"<iframe src=\"https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d2624.9983685732213!2d2.29432085!3d48.85824149999999!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2s!4v1385737436368\" width=\"100\" height=\"42\"></iframe>",
|
||||
"it allows iframe to google maps");
|
||||
|
||||
cooked("<iframe width=\"425\" height=\"350\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" src=\"http://www.openstreetmap.org/export/embed.html?bbox=22.49454975128174%2C51.220338322410775%2C22.523088455200195%2C51.23345342732931&layer=mapnik\"></iframe>",
|
||||
"<iframe width=\"425\" height=\"350\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" src=\"http://www.openstreetmap.org/export/embed.html?bbox=22.49454975128174%2C51.220338322410775%2C22.523088455200195%2C51.23345342732931&layer=mapnik\"></iframe>",
|
||||
"it allows iframe to OpenStreetMap");
|
||||
|
||||
equal(pt.sanitize("<textarea>hullo</textarea>"), "hullo");
|
||||
equal(pt.sanitize("<button>press me!</button>"), "press me!");
|
||||
equal(pt.sanitize("<canvas>draw me!</canvas>"), "draw me!");
|
||||
equal(pt.sanitize("<progress>hello"), "hello");
|
||||
equal(pt.sanitize("<mark>highlight</mark>"), "highlight");
|
||||
|
||||
cooked("[the answer](javascript:alert(42))", "<p><a>the answer</a></p>", "it prevents XSS");
|
||||
|
||||
cooked("<i class=\"fa fa-bug fa-spin\" style=\"font-size:600%\"></i>\n<!-- -->", "<p><i></i><br/></p>", "it doesn't circumvent XSS with comments");
|
||||
|
||||
cooked("<span class=\"-bbcode-s fa fa-spin\">a</span>", "<p><span>a</span></p>", "it sanitizes spans");
|
||||
cooked("<span class=\"fa fa-spin -bbcode-s\">a</span>", "<p><span>a</span></p>", "it sanitizes spans");
|
||||
cooked("<span class=\"bbcode-s\">a</span>", "<p><span class=\"bbcode-s\">a</span></p>", "it sanitizes spans");
|
||||
});
|
||||
|
||||
test("urlAllowed", function() {
|
||||
const allowed = (url, msg) => equal(hrefAllowed(url), url, msg);
|
||||
|
||||
allowed("/foo/bar.html", "allows relative urls");
|
||||
allowed("http://eviltrout.com/evil/trout", "allows full urls");
|
||||
allowed("https://eviltrout.com/evil/trout", "allows https urls");
|
||||
allowed("//eviltrout.com/evil/trout", "allows protocol relative urls");
|
||||
|
||||
equal(hrefAllowed("http://google.com/test'onmouseover=alert('XSS!');//.swf"),
|
||||
"http://google.com/test%27onmouseover=alert(%27XSS!%27);//.swf",
|
||||
"escape single quotes");
|
||||
});
|
||||
|
@ -1,16 +1,27 @@
|
||||
/* global Int8Array:true */
|
||||
import { blank } from 'helpers/qunit-helpers';
|
||||
import {
|
||||
emailValid,
|
||||
isAnImage,
|
||||
avatarUrl,
|
||||
allowsAttachments,
|
||||
getRawSize,
|
||||
avatarImg,
|
||||
defaultHomepage,
|
||||
validateUploadedFiles,
|
||||
getUploadMarkdown,
|
||||
caretRowCol,
|
||||
setCaretPosition
|
||||
} from 'discourse/lib/utilities';
|
||||
|
||||
module("Discourse.Utilities");
|
||||
|
||||
var utils = Discourse.Utilities;
|
||||
module("lib:utilities");
|
||||
|
||||
test("emailValid", function() {
|
||||
ok(utils.emailValid('Bob@example.com'), "allows upper case in the first part of emails");
|
||||
ok(utils.emailValid('bob@EXAMPLE.com'), "allows upper case in the email domain");
|
||||
ok(emailValid('Bob@example.com'), "allows upper case in the first part of emails");
|
||||
ok(emailValid('bob@EXAMPLE.com'), "allows upper case in the email domain");
|
||||
});
|
||||
|
||||
var validUpload = utils.validateUploadedFiles;
|
||||
var validUpload = validateUploadedFiles;
|
||||
|
||||
test("validateUploadedFiles", function() {
|
||||
not(validUpload(null), "no files are invalid");
|
||||
@ -80,8 +91,8 @@ test("allows valid uploads to go through", function() {
|
||||
not(bootbox.alert.calledOnce);
|
||||
});
|
||||
|
||||
var getUploadMarkdown = function(filename) {
|
||||
return utils.getUploadMarkdown({
|
||||
var testUploadMarkdown = function(filename) {
|
||||
return getUploadMarkdown({
|
||||
original_filename: filename,
|
||||
filesize: 42,
|
||||
width: 100,
|
||||
@ -91,26 +102,26 @@ var getUploadMarkdown = function(filename) {
|
||||
};
|
||||
|
||||
test("getUploadMarkdown", function() {
|
||||
ok(getUploadMarkdown("lolcat.gif") === '<img src="/uploads/123/abcdef.ext" width="100" height="200">');
|
||||
ok(getUploadMarkdown("important.txt") === '<a class="attachment" href="/uploads/123/abcdef.ext">important.txt</a> (42 Bytes)\n');
|
||||
ok(testUploadMarkdown("lolcat.gif") === '<img src="/uploads/123/abcdef.ext" width="100" height="200">');
|
||||
ok(testUploadMarkdown("important.txt") === '<a class="attachment" href="/uploads/123/abcdef.ext">important.txt</a> (42 Bytes)\n');
|
||||
});
|
||||
|
||||
test("isAnImage", function() {
|
||||
_.each(["png", "jpg", "jpeg", "bmp", "gif", "tif", "tiff", "ico"], function(extension) {
|
||||
var image = "image." + extension;
|
||||
ok(utils.isAnImage(image), image + " is recognized as an image");
|
||||
ok(utils.isAnImage("http://foo.bar/path/to/" + image), image + " is recognized as an image");
|
||||
ok(isAnImage(image), image + " is recognized as an image");
|
||||
ok(isAnImage("http://foo.bar/path/to/" + image), image + " is recognized as an image");
|
||||
});
|
||||
not(utils.isAnImage("file.txt"));
|
||||
not(utils.isAnImage("http://foo.bar/path/to/file.txt"));
|
||||
not(utils.isAnImage(""));
|
||||
not(isAnImage("file.txt"));
|
||||
not(isAnImage("http://foo.bar/path/to/file.txt"));
|
||||
not(isAnImage(""));
|
||||
});
|
||||
|
||||
test("avatarUrl", function() {
|
||||
var rawSize = utils.getRawSize;
|
||||
blank(utils.avatarUrl('', 'tiny'), "no template returns blank");
|
||||
equal(utils.avatarUrl('/fake/template/{size}.png', 'tiny'), "/fake/template/" + rawSize(20) + ".png", "simple avatar url");
|
||||
equal(utils.avatarUrl('/fake/template/{size}.png', 'large'), "/fake/template/" + rawSize(45) + ".png", "different size");
|
||||
var rawSize = getRawSize;
|
||||
blank(avatarUrl('', 'tiny'), "no template returns blank");
|
||||
equal(avatarUrl('/fake/template/{size}.png', 'tiny'), "/fake/template/" + rawSize(20) + ".png", "simple avatar url");
|
||||
equal(avatarUrl('/fake/template/{size}.png', 'large'), "/fake/template/" + rawSize(45) + ".png", "different size");
|
||||
});
|
||||
|
||||
var setDevicePixelRatio = function(value) {
|
||||
@ -126,19 +137,19 @@ test("avatarImg", function() {
|
||||
setDevicePixelRatio(2);
|
||||
|
||||
var avatarTemplate = "/path/to/avatar/{size}.png";
|
||||
equal(utils.avatarImg({avatarTemplate: avatarTemplate, size: 'tiny'}),
|
||||
equal(avatarImg({avatarTemplate: avatarTemplate, size: 'tiny'}),
|
||||
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar'>",
|
||||
"it returns the avatar html");
|
||||
|
||||
equal(utils.avatarImg({avatarTemplate: avatarTemplate, size: 'tiny', title: 'evilest trout'}),
|
||||
equal(avatarImg({avatarTemplate: avatarTemplate, size: 'tiny', title: 'evilest trout'}),
|
||||
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar' title='evilest trout'>",
|
||||
"it adds a title if supplied");
|
||||
|
||||
equal(utils.avatarImg({avatarTemplate: avatarTemplate, size: 'tiny', extraClasses: 'evil fish'}),
|
||||
equal(avatarImg({avatarTemplate: avatarTemplate, size: 'tiny', extraClasses: 'evil fish'}),
|
||||
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar evil fish'>",
|
||||
"it adds extra classes if supplied");
|
||||
|
||||
blank(utils.avatarImg({avatarTemplate: "", size: 'tiny'}),
|
||||
blank(avatarImg({avatarTemplate: "", size: 'tiny'}),
|
||||
"it doesn't render avatars for invalid avatar template");
|
||||
|
||||
setDevicePixelRatio(oldRatio);
|
||||
@ -146,18 +157,18 @@ test("avatarImg", function() {
|
||||
|
||||
test("allowsAttachments", function() {
|
||||
Discourse.SiteSettings.authorized_extensions = "jpg|jpeg|gif";
|
||||
not(utils.allowsAttachments(), "no attachments allowed by default");
|
||||
not(allowsAttachments(), "no attachments allowed by default");
|
||||
|
||||
Discourse.SiteSettings.authorized_extensions = "jpg|jpeg|gif|*";
|
||||
ok(utils.allowsAttachments(), "attachments are allowed when all extensions are allowed");
|
||||
ok(allowsAttachments(), "attachments are allowed when all extensions are allowed");
|
||||
|
||||
Discourse.SiteSettings.authorized_extensions = "jpg|jpeg|gif|pdf";
|
||||
ok(utils.allowsAttachments(), "attachments are allowed when at least one extension is not an image extension");
|
||||
ok(allowsAttachments(), "attachments are allowed when at least one extension is not an image extension");
|
||||
});
|
||||
|
||||
test("defaultHomepage", function() {
|
||||
Discourse.SiteSettings.top_menu = "latest|top|hot";
|
||||
equal(utils.defaultHomepage(), "latest", "default homepage is the first item in the top_menu site setting");
|
||||
equal(defaultHomepage(), "latest", "default homepage is the first item in the top_menu site setting");
|
||||
});
|
||||
|
||||
test("caretRowCol", () => {
|
||||
@ -167,9 +178,9 @@ test("caretRowCol", () => {
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
const assertResult = (setCaretPos, expectedRowNum, expectedColNum) => {
|
||||
Discourse.Utilities.setCaretPosition(textarea, setCaretPos);
|
||||
setCaretPosition(textarea, setCaretPos);
|
||||
|
||||
const result = Discourse.Utilities.caretRowCol(textarea);
|
||||
const result = caretRowCol(textarea);
|
||||
equal(result.rowNum, expectedRowNum, "returns the right row of the caret");
|
||||
equal(result.colNum, expectedColNum, "returns the right col of the caret");
|
||||
};
|
||||
|
@ -1,8 +1,9 @@
|
||||
module("MDTest", {
|
||||
setup: function() {
|
||||
Discourse.SiteSettings.traditional_markdown_linebreaks = false;
|
||||
}
|
||||
});
|
||||
import { sanitize } from 'pretty-text/sanitizer';
|
||||
import { default as PrettyText, buildOptions } from 'pretty-text/pretty-text';
|
||||
import { hashString } from 'discourse/lib/hash';
|
||||
|
||||
// Run the MDTest spec
|
||||
module("MDTest");
|
||||
|
||||
// This is cheating, but the trivial differences between sanitization
|
||||
// do not affect formatting.
|
||||
@ -15,44 +16,42 @@ function normalize(str) {
|
||||
|
||||
// We use a custom sanitizer for MD test that hoists out comments. In Discourse
|
||||
// they are stripped, but to be compliant with the spec they should not be.
|
||||
function hoistingSanitizer(result) {
|
||||
var hoisted,
|
||||
m = result.match(/<!--[\s\S]*?-->/g);
|
||||
function sanitizer(result, whiteLister) {
|
||||
let hoisted;
|
||||
const m = result.match(/<!--[\s\S]*?-->/g);
|
||||
if (m && m.length) {
|
||||
hoisted = [];
|
||||
for (var i=0; i<m.length; i++) {
|
||||
var c = m[i],
|
||||
id = md5("discourse:hoisted-comment:" + i);
|
||||
for (let i=0; i<m.length; i++) {
|
||||
const c = m[i];
|
||||
const id = hashString("discourse:hoisted-comment:" + i).toString();
|
||||
result = result.replace(c, id);
|
||||
hoisted.push([c, id]);
|
||||
}
|
||||
}
|
||||
|
||||
result = Discourse.Markdown.sanitize(result);
|
||||
|
||||
result = sanitize(result, whiteLister);
|
||||
if (hoisted) {
|
||||
hoisted.forEach(function(tuple) {
|
||||
result = result.replace(tuple[1], tuple[0]);
|
||||
});
|
||||
hoisted.forEach(tuple => result = result.replace(tuple[1], tuple[0]));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var md = function(input, expected, text) {
|
||||
var result = Discourse.Markdown.cook(input, {
|
||||
sanitizerFunction: hoistingSanitizer,
|
||||
traditional_markdown_linebreaks: true
|
||||
}),
|
||||
resultNorm = normalize(result),
|
||||
expectedNorm = normalize(expected),
|
||||
same = (result === expected) || (resultNorm === expectedNorm);
|
||||
function md(input, expected, text) {
|
||||
|
||||
const opts = buildOptions({ siteSettings: {} });
|
||||
opts.traditionalMarkdownLinebreaks = true;
|
||||
opts.sanitizer = sanitizer;
|
||||
|
||||
const cooker = new PrettyText(opts);
|
||||
const result = cooker.cook(input);
|
||||
const resultNorm = normalize(result);
|
||||
const expectedNorm = normalize(expected);
|
||||
const same = (result === expected) || (resultNorm === expectedNorm);
|
||||
|
||||
if (same) {
|
||||
ok(same, text);
|
||||
} else {
|
||||
console.log(resultNorm);
|
||||
console.log(expectedNorm);
|
||||
equal(resultNorm, expectedNorm, text);
|
||||
}
|
||||
};
|
@ -1,4 +1,6 @@
|
||||
import { blank, present } from 'helpers/qunit-helpers';
|
||||
import { IMAGE_VERSION as v} from 'pretty-text/emoji';
|
||||
|
||||
module("model:topic");
|
||||
|
||||
import Topic from 'discourse/models/topic';
|
||||
@ -75,7 +77,6 @@ test("recover", function() {
|
||||
|
||||
test('fancyTitle', function() {
|
||||
var topic = Topic.create({ fancy_title: ":smile: with all :) the emojis :pear::peach:" });
|
||||
const v = Discourse.Emoji.ImageVersion;
|
||||
|
||||
equal(topic.get('fancyTitle'),
|
||||
`<img src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'> with all <img src='/images/emoji/emoji_one/slight_smile.png?v=${v}' title='slight_smile' alt='slight_smile' class='emoji'> the emojis <img src='/images/emoji/emoji_one/pear.png?v=${v}' title='pear' alt='pear' class='emoji'><img src='/images/emoji/emoji_one/peach.png?v=${v}' title='peach' alt='peach' class='emoji'>`,
|
||||
|
@ -22,9 +22,9 @@
|
||||
//= require htmlparser.js
|
||||
|
||||
// Stuff we need to load first
|
||||
//= require pretty-text-bundle
|
||||
//= require main_include
|
||||
//= require admin
|
||||
//= require_tree ../../app/assets/javascripts/defer
|
||||
|
||||
//= require sinon-1.7.1
|
||||
//= require sinon-qunit-1.0.0
|
||||
@ -43,12 +43,6 @@
|
||||
|
||||
window.inTestEnv = true;
|
||||
|
||||
window.assetPath = function(url) {
|
||||
if (url.indexOf('defer') === 0) {
|
||||
return "/assets/" + url;
|
||||
}
|
||||
};
|
||||
|
||||
// Stop the message bus so we don't get ajax calls
|
||||
window.MessageBus.stop();
|
||||
|
||||
@ -137,3 +131,5 @@ Object.keys(requirejs.entries).forEach(function(entry) {
|
||||
require(entry, null, null, true);
|
||||
}
|
||||
});
|
||||
require('mdtest/mdtest', null, null, true);
|
||||
|
||||
|
Reference in New Issue
Block a user