Renamed components to lib in the JS project, as Ember has components and they mean something different.

This commit is contained in:
Robin Ward
2013-10-24 12:36:46 -04:00
parent 3909f93a7e
commit 9adcd1579d
41 changed files with 15 additions and 10 deletions

View File

@ -1,123 +0,0 @@
/*global md5:true */
module("Discourse.BBCode");
var format = function(input, expected, text) {
var cooked = Discourse.Markdown.cook(input, {lookupAvatar: false});
equal(cooked, "<p>" + expected + "</p>", 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("[spoiler]it's a sled[/spoiler]", "<span class=\"spoiler\">it's a sled</span>", "supports spoiler tags");
format("[img]http://eviltrout.com/eviltrout.png[/img]", "<img src=\"http://eviltrout.com/eviltrout.png\"/>", "links images");
format("[url]http://bettercallsaul.com[/url]", "<a href=\"http://bettercallsaul.com\">http://bettercallsaul.com</a>", "supports [url] without a title");
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");
});
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>\nx++</pre>", "makes code into pre");
format("[code]\nx++\ny++\nz++\n[/code]", "<pre>\nx++\ny++\nz++</pre>", "makes code into pre");
format("[code]abc\n#def\n[/code]", '<pre>abc\n#def</pre>', 'it handles headings in a [code] block');
});
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");
});
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("size tags", function() {
format("[size=35]BIG [b]whoop[/b][/size]",
"<span class=\"bbcode-size-35\">BIG <span class=\"bbcode-b\">whoop</span></span>",
"supports [size=]");
format("[size=asdf]regular[/size]",
"<span class=\"bbcode-size-1\">regular</span>",
"it only supports numbers in bbcode");
});
test("quotes", function() {
var post = Discourse.Post.create({
cooked: "<p><b>lorem</b> ipsum</p>",
username: "eviltrout",
post_number: 1,
topic_id: 2
});
var formatQuote = function(val, expected, text) {
equal(Discourse.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 &lt;not&gt; 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");
});
test("quote formatting", function() {
format("[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 said:</div><blockquote><p>[sam]</p></blockquote></aside>",
"it allows quotes with [] inside");
format("[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 said:" +
"</div><blockquote><p>abc</p></blockquote></aside>",
"renders quotes properly");
format("[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 said:" +
"</div><blockquote><p>abc</p></blockquote></aside></p>\n\n<p>hello",
"handles new lines properly");
});
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,
"<p><aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">" +
"<div class=\"quote-controls\"></div>EvilTrout said:</div><blockquote><p>hello</p></blockquote></aside></p>\n\n<p><em>Test</em></p>",
"it allows trailing formatting");
});

View File

@ -1,179 +0,0 @@
module("Discourse.ClickTrack", {
setup: function() {
// Prevent any of these tests from navigating away
this.win = {focus: function() { } };
this.redirectTo = sinon.stub(Discourse.URL, "redirectTo");
sinon.stub(Discourse, "ajax");
this.windowOpen = sinon.stub(window, "open").returns(this.win);
sinon.stub(this.win, "focus");
$('#qunit-scratch').html([
'<div id="topic" id="1337">',
' <article data-post-id="42" data-user-id="3141">',
' <a href="http://www.google.com">google.com</a>',
' <a class="lightbox back quote-other-topic" href="http://www.google.com">google.com</a>',
' <a id="with-badge" data-user-id="314" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <a id="with-badge-but-not-mine" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <div class="onebox-result">',
' <a id="inside-onebox" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <a id="inside-onebox-forced" class="track-link" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' </div>',
' <a id="same-site" href="http://discuss.domain.com">forum</a>',
' <a class="attachment" href="http://discuss.domain.com/uploads/default/1234/1532357280.txt">log.txt</a>',
' </article>',
'</div>'].join("\n"));
},
teardown: function() {
$('#topic').remove();
$('#qunit-scratch').html('');
Discourse.URL.redirectTo.restore();
Discourse.ajax.restore();
window.open.restore();
this.win.focus.restore();
}
});
var track = Discourse.ClickTrack.trackClick;
// test
var generateClickEventOn = function(selector) {
return $.Event("click", { currentTarget: $(selector)[0] });
};
test("does not track clicks on lightboxes", function() {
var clickEvent = generateClickEventOn('.lightbox');
this.stub(clickEvent, "preventDefault");
ok(track(clickEvent));
ok(!clickEvent.preventDefault.calledOnce);
});
test("it calls preventDefault when clicking on an a", function() {
var clickEvent = generateClickEventOn('a');
this.stub(clickEvent, "preventDefault");
track(clickEvent);
ok(clickEvent.preventDefault.calledOnce);
ok(Discourse.URL.redirectTo.calledOnce);
});
test("does not track clicks on back buttons", function() {
ok(track(generateClickEventOn('.back')));
});
test("does not track clicks on quote buttons", function() {
ok(track(generateClickEventOn('.quote-other-topic')));
});
test("removes the href and put it as a data attribute", function() {
track(generateClickEventOn('a'));
var $link = $('a').first();
ok($link.hasClass('no-href'));
equal($link.data('href'), 'http://www.google.com');
blank($link.attr('href'));
ok($link.data('auto-route'));
ok(Discourse.URL.redirectTo.calledOnce);
});
var badgeClickCount = function(id, expected) {
track(generateClickEventOn('#' + id));
var $badge = $('span.badge', $('#' + id).first());
equal(parseInt($badge.html(), 10), expected);
};
test("does not update badge clicks on my own link", function() {
this.stub(Discourse.User, 'currentProp').withArgs('id').returns(314);
badgeClickCount('with-badge', 1);
});
test("does not update badge clicks in my own post", function() {
this.stub(Discourse.User, 'currentProp').withArgs('id').returns(3141);
badgeClickCount('with-badge-but-not-mine', 1);
});
test("updates badge counts correctly", function() {
badgeClickCount('inside-onebox', 1);
badgeClickCount('inside-onebox-forced', 2);
badgeClickCount('with-badge', 2);
});
var trackRightClick = function() {
var clickEvent = generateClickEventOn('a');
clickEvent.which = 3;
return track(clickEvent);
};
test("right clicks change the href", function() {
ok(trackRightClick());
equal($('a').first().prop('href'), "http://www.google.com/");
});
test("right clicks are tracked", function() {
Discourse.SiteSettings.track_external_right_clicks = true;
trackRightClick();
equal($('a').first().attr('href'), "/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42");
});
var expectToOpenInANewTab = function(clickEvent) {
ok(!track(clickEvent));
ok(Discourse.ajax.calledOnce);
ok(window.open.calledOnce);
};
test("it opens in a new tab when pressing shift", function() {
var clickEvent = generateClickEventOn('a');
clickEvent.shiftKey = true;
expectToOpenInANewTab(clickEvent);
});
test("it opens in a new tab when pressing meta", function() {
var clickEvent = generateClickEventOn('a');
clickEvent.metaKey = true;
expectToOpenInANewTab(clickEvent);
});
test("it opens in a new tab when pressing meta", function() {
var clickEvent = generateClickEventOn('a');
clickEvent.ctrlKey = true;
expectToOpenInANewTab(clickEvent);
});
test("it opens in a new tab when pressing meta", function() {
var clickEvent = generateClickEventOn('a');
clickEvent.which = 2;
expectToOpenInANewTab(clickEvent);
});
test("tracks via AJAX if we're on the same site", function() {
this.stub(Discourse.URL, "routeTo");
this.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");
ok(!track(generateClickEventOn('#same-site')));
ok(Discourse.ajax.calledOnce);
ok(Discourse.URL.routeTo.calledOnce);
});
test("does not track via AJAX for attachments", function() {
this.stub(Discourse.URL, "routeTo");
this.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");
ok(!track(generateClickEventOn('.attachment')));
ok(Discourse.URL.redirectTo.calledOnce);
});
test("tracks custom urls when opening in another window", function() {
var clickEvent = generateClickEventOn('a');
this.stub(Discourse.User, "currentProp").withArgs('external_links_in_new_tab').returns(true);
ok(!track(clickEvent));
ok(this.windowOpen.calledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42', '_blank'));
});
test("tracks custom urls when opening in another window", function() {
var clickEvent = generateClickEventOn('a');
ok(!track(clickEvent));
ok(this.redirectTo.calledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42'));
});

View File

@ -1,91 +0,0 @@
module("Discourse.Computed", {
setup: function() {
sinon.stub(I18n, "t", function(scope) {
return "%@ translated: " + scope;
});
},
teardown: function() {
I18n.t.restore();
}
});
test("propertyEqual", function() {
var t = Em.Object.extend({
same: Discourse.computed.propertyEqual('cookies', 'biscuits')
}).create({
cookies: 10,
biscuits: 10
});
ok(t.get('same'), "it is true when the properties are the same");
t.set('biscuits', 9);
ok(!t.get('same'), "it isn't true when one property is different");
});
test("propertyNotEqual", function() {
var t = Em.Object.extend({
diff: Discourse.computed.propertyNotEqual('cookies', 'biscuits')
}).create({
cookies: 10,
biscuits: 10
});
ok(!t.get('diff'), "it isn't true when the properties are the same");
t.set('biscuits', 9);
ok(t.get('diff'), "it is true when one property is different");
});
test("fmt", function() {
var t = Em.Object.extend({
exclaimyUsername: Discourse.computed.fmt('username', "!!! %@ !!!"),
multiple: Discourse.computed.fmt('username', 'mood', "%@ is %@")
}).create({
username: 'eviltrout',
mood: "happy"
});
equal(t.get('exclaimyUsername'), '!!! eviltrout !!!', "it inserts the string");
equal(t.get('multiple'), "eviltrout is happy", "it inserts multiple strings");
t.set('username', 'codinghorror');
equal(t.get('multiple'), "codinghorror is happy", "it supports changing properties");
t.set('mood', 'ecstatic');
equal(t.get('multiple'), "codinghorror is ecstatic", "it supports changing another property");
});
test("i18n", function() {
var t = Em.Object.extend({
exclaimyUsername: Discourse.computed.i18n('username', "!!! %@ !!!"),
multiple: Discourse.computed.i18n('username', 'mood', "%@ is %@")
}).create({
username: 'eviltrout',
mood: "happy"
});
equal(t.get('exclaimyUsername'), '%@ translated: !!! eviltrout !!!', "it inserts the string and then translates");
equal(t.get('multiple'), "%@ translated: eviltrout is happy", "it inserts multiple strings and then translates");
t.set('username', 'codinghorror');
equal(t.get('multiple'), "%@ translated: codinghorror is happy", "it supports changing properties");
t.set('mood', 'ecstatic');
equal(t.get('multiple'), "%@ translated: codinghorror is ecstatic", "it supports changing another property");
});
test("url", function() {
var t, testClass;
testClass = Em.Object.extend({
userUrl: Discourse.computed.url('username', "/users/%@")
});
t = testClass.create({ username: 'eviltrout' });
equal(t.get('userUrl'), "/users/eviltrout", "it supports urls without a prefix");
Discourse.BaseUri = "/prefixed/";
t = testClass.create({ username: 'eviltrout' });
equal(t.get('userUrl'), "/prefixed/users/eviltrout", "it supports urls with a prefix");
});

View File

@ -1,94 +0,0 @@
var clock, original, debounced, originalPromiseResolvesWith, callback;
var nothingFired = function(additionalMessage) {
ok(!original.called, "original function is not called " + additionalMessage);
ok(!callback.called, "debounced promise is not resolved " + additionalMessage);
};
var originalAndCallbackFiredOnce = function(additionalMessage) {
ok(original.calledOnce, "original function is called once " + additionalMessage);
ok(callback.calledOnce, "debounced promise is resolved once " + additionalMessage);
};
module("Discourse.debouncePromise", {
setup: function() {
clock = sinon.useFakeTimers();
originalPromiseResolvesWith = null;
original = sinon.spy(function() {
var promise = Ember.Deferred.create();
promise.resolve(originalPromiseResolvesWith);
return promise;
});
debounced = Discourse.debouncePromise(original, 100);
callback = sinon.spy();
},
teardown: function() {
clock.restore();
}
});
test("delays execution till the end of the timeout", function() {
debounced().then(callback);
nothingFired("immediately after calling debounced function");
clock.tick(99);
nothingFired("just before the end of the timeout");
clock.tick(1);
originalAndCallbackFiredOnce("exactly at the end of the timeout");
});
test("executes only once, no matter how many times debounced function is called during the timeout", function() {
debounced().then(callback);
debounced().then(callback);
clock.tick(100);
originalAndCallbackFiredOnce("(second call was supressed)");
});
test("prolongs the timeout when the debounced function is called for the second time during the timeout", function() {
debounced().then(callback);
clock.tick(50);
debounced().then(callback);
clock.tick(50);
nothingFired("at the end of the original timeout");
clock.tick(50);
originalAndCallbackFiredOnce("exactly at the end of the prolonged timeout");
});
test("preserves last call's context and params when executing delayed function", function() {
var firstObj = {};
var secondObj = {};
debounced.call(firstObj, "first");
debounced.call(secondObj, "second");
clock.tick(100);
ok(original.calledOn(secondObj), "the context of the second of two subsequent calls is preserved");
ok(original.calledWithExactly("second"), "param passed during the second of two subsequent calls is preserved");
});
test("can be called again after timeout passes", function() {
debounced().then(callback);
clock.tick(100);
debounced().then(callback);
clock.tick(100);
ok(original.calledTwice, "original function is called for the second time");
ok(callback.calledTwice, "debounced promise is resolved for the second time");
});
test("passes resolved value from the original promise as a param to the debounced promise's callback", function() {
originalPromiseResolvesWith = "original promise return value";
debounced().then(callback);
clock.tick(100);
ok(callback.calledWith("original promise return value"));
});

View File

@ -1,82 +0,0 @@
var clock, original, debounced;
var firedOnce = function(message) {
ok(original.calledOnce, message);
};
var firedTwice = function(message) {
ok(original.calledTwice, message);
};
var notFired = function(message) {
ok(!original.called, message);
};
module("Discourse.debounce", {
setup: function() {
clock = sinon.useFakeTimers();
original = sinon.spy();
debounced = Discourse.debounce(original, 100);
},
teardown: function() {
clock.restore();
}
});
test("delays function execution till the end of the timeout", function() {
debounced();
notFired("immediately after calling debounced function nothing happens");
clock.tick(99);
notFired("just before the end of the timeout still nothing happens");
clock.tick(1);
firedOnce("exactly at the end of the timeout the function is executed");
});
test("executes delayed function only once, no matter how many times debounced function is called during the timeout", function() {
debounced();
debounced();
clock.tick(100);
firedOnce("second call was supressed");
});
test("prolongs the timeout when the debounced function is called for the second time during the timeout", function() {
debounced();
clock.tick(50);
debounced();
clock.tick(50);
notFired("at the end of the original timeout nothing happens");
clock.tick(50);
firedOnce("function is executed exactly at the end of the prolonged timeout");
});
test("preserves last call's context and params when executing delayed function", function() {
var firstObj = {};
var secondObj = {};
debounced.call(firstObj, "first");
debounced.call(secondObj, "second");
clock.tick(100);
ok(original.calledOn(secondObj), "the context of the last of two subsequent calls is preserved");
ok(original.calledWithExactly("second"), "param passed during the last of two subsequent calls is preserved");
});
test("can be called again after timeout passes", function() {
var firstObj = {};
var secondObj = {};
debounced.call(firstObj, "first");
clock.tick(100);
debounced.call(secondObj, "second");
clock.tick(100);
firedTwice();
});

View File

@ -1,204 +0,0 @@
var clock;
module("Discourse.Formatter", {
setup: function() {
clock = sinon.useFakeTimers(new Date(2012,11,31,12,0).getTime());
},
teardown: function() {
clock.restore();
}
});
var format = "tiny";
var leaveAgo = false;
var mins_ago = function(mins){
return new Date((new Date()) - mins * 60 * 1000);
};
var formatMins = function(mins) {
return Discourse.Formatter.relativeAge(mins_ago(mins), {format: format, leaveAgo: leaveAgo});
};
var formatHours = function(hours) {
return formatMins(hours * 60);
};
var formatDays = function(days) {
return formatHours(days * 24);
};
var formatMonths = function(months) {
return formatDays(months * 30);
};
var shortDate = function(days){
return moment().subtract('days', days).format('D MMM');
};
test("formating medium length dates", function() {
format = "medium";
var strip = function(html){
return $(html).text();
};
var shortDateYear = function(days){
return moment().subtract('days', days).format('D MMM, YYYY');
};
leaveAgo = true;
equal(strip(formatMins(1.4)), "1 min ago");
equal(strip(formatMins(2)), "2 mins ago");
equal(strip(formatMins(56)), "56 mins ago");
equal(strip(formatMins(57)), "1 hour ago");
equal(strip(formatHours(4)), "4 hours ago");
equal(strip(formatHours(22)), "22 hours ago");
equal(strip(formatHours(23)), "1 day ago");
equal(strip(formatDays(4.85)), "4 days ago");
leaveAgo = false;
equal(strip(formatMins(0)), "just now");
equal(strip(formatMins(1.4)), "1 min");
equal(strip(formatMins(2)), "2 mins");
equal(strip(formatMins(56)), "56 mins");
equal(strip(formatMins(57)), "1 hour");
equal(strip(formatHours(4)), "4 hours");
equal(strip(formatHours(22)), "22 hours");
equal(strip(formatHours(23)), "1 day");
equal(strip(formatDays(4.85)), "4 days");
equal(strip(formatDays(6)), shortDate(6));
equal(strip(formatDays(100)), shortDate(100)); // eg: 23 Jan
equal(strip(formatDays(500)), shortDateYear(500));
equal($(formatDays(0)).attr("title"), moment().format('MMMM D, YYYY h:mma'));
equal($(formatDays(0)).attr("class"), "date");
clock.restore();
clock = sinon.useFakeTimers(new Date(2012,0,9,12,0).getTime()); // Jan 9, 2012
equal(strip(formatDays(8)), shortDate(8));
equal(strip(formatDays(10)), shortDateYear(10));
});
test("formating tiny dates", function() {
var shortDateYear = function(days){
return moment().subtract('days', days).format("D MMM 'YY");
};
format = "tiny";
equal(formatMins(0), "< 1m");
equal(formatMins(2), "2m");
equal(formatMins(60), "1h");
equal(formatHours(4), "4h");
equal(formatDays(1), "1d");
equal(formatDays(14), "14d");
equal(formatDays(15), shortDate(15));
equal(formatDays(92), shortDate(92));
equal(formatDays(364), shortDate(364));
equal(formatDays(365), shortDate(365));
equal(formatDays(366), shortDateYear(366)); // leap year
equal(formatDays(500), shortDateYear(500));
equal(formatDays(365*2 + 1), shortDateYear(365*2 + 1)); // one leap year
var originalValue = Discourse.SiteSettings.relative_date_duration;
Discourse.SiteSettings.relative_date_duration = 7;
equal(formatDays(7), "7d");
equal(formatDays(8), shortDate(8));
Discourse.SiteSettings.relative_date_duration = 1;
equal(formatDays(1), "1d");
equal(formatDays(2), shortDate(2));
Discourse.SiteSettings.relative_date_duration = 0;
equal(formatMins(0), "< 1m");
equal(formatMins(2), "2m");
equal(formatMins(60), "1h");
equal(formatDays(1), shortDate(1));
equal(formatDays(2), shortDate(2));
equal(formatDays(366), shortDateYear(366));
Discourse.SiteSettings.relative_date_duration = null;
equal(formatDays(1), '1d');
equal(formatDays(14), '14d');
equal(formatDays(15), shortDate(15));
Discourse.SiteSettings.relative_date_duration = 14;
clock.restore();
clock = sinon.useFakeTimers(new Date(2012,0,12,12,0).getTime()); // Jan 12, 2012
equal(formatDays(11), "11d");
equal(formatDays(14), "14d");
equal(formatDays(15), shortDateYear(15));
equal(formatDays(366), shortDateYear(366));
clock.restore();
clock = sinon.useFakeTimers(new Date(2012,0,20,12,0).getTime()); // Jan 20, 2012
equal(formatDays(14), "14d");
equal(formatDays(15), shortDate(15));
equal(formatDays(20), shortDateYear(20));
Discourse.SiteSettings.relative_date_duration = originalValue;
});
module("Discourse.Formatter");
test("autoUpdatingRelativeAge", function() {
var d = moment().subtract('days',1).toDate();
var $elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d));
equal($elem.data('format'), "tiny");
equal($elem.data('time'), d.getTime());
equal($elem.attr('title'), undefined);
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d, {title: true}));
equal($elem.attr('title'), moment(d).longDate());
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d,{format: 'medium', title: true, leaveAgo: true}));
equal($elem.data('format'), "medium-with-ago");
equal($elem.data('time'), d.getTime());
equal($elem.attr('title'), moment(d).longDate());
equal($elem.html(), '1 day ago');
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d,{format: 'medium'}));
equal($elem.data('format'), "medium");
equal($elem.data('time'), d.getTime());
equal($elem.attr('title'), undefined);
equal($elem.html(), '1 day');
});
test("updateRelativeAge", function(){
var d = new Date();
var $elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d));
$elem.data('time', d.getTime() - 2 * 60 * 1000);
Discourse.Formatter.updateRelativeAge($elem);
equal($elem.html(), "2m");
d = new Date();
$elem = $(Discourse.Formatter.autoUpdatingRelativeAge(d, {format: 'medium', leaveAgo: true}));
$elem.data('time', d.getTime() - 2 * 60 * 1000);
Discourse.Formatter.updateRelativeAge($elem);
equal($elem.html(), "2 mins ago");
});
test("breakUp", function(){
var b = function(s){ return Discourse.Formatter.breakUp(s,5); };
equal(b("hello"), "hello");
equal(b("helloworld"), "hello world");
equal(b("HeMans"), "He Mans");
equal(b("he_man"), "he_ man");
equal(b("he11111"), "he 11111");
equal(b("HRCBob"), "HRC Bob");
});

View File

@ -1,20 +0,0 @@
var store = Discourse.KeyValueStore;
module("Discourse.KeyValueStore", {
setup: function() {
store.init("test");
}
});
test("it's able to get the result back from the store", function() {
store.set({ key: "bob", value: "uncle" });
equal(store.get("bob"), "uncle");
});
test("is able to nuke the store", function() {
store.set({ key: "bob1", value: "uncle" });
store.abandonLocal();
localStorage.a = 1;
equal(store.get("bob1"), void 0);
equal(localStorage.a, "1");
});

View File

@ -1,358 +0,0 @@
/*global sanitizeHtml:true */
module("Discourse.Markdown", {
setup: function() {
Discourse.SiteSettings.traditional_markdown_linebreaks = false;
}
});
var cooked = function(input, expected, text) {
var result = Discourse.Markdown.cook(input, {mentionLookup: false, sanitize: true});
if (result !== expected) {
console.log(JSON.stringify(result));
console.log(JSON.stringify(expected));
}
equal(result, expected, text);
};
var cookedOptions = function(input, opts, expected, text) {
equal(Discourse.Markdown.cook(input, opts), expected, text);
};
test("basic cooking", function() {
cooked("hello", "<p>hello</p>", "surrounds text with paragraphs");
cooked("**evil**", "<p><strong>evil</strong></p>", "it bolds text.");
cooked("__bold__", "<p><strong>bold</strong></p>", "it bolds text.");
cooked("*trout*", "<p><em>trout</em></p>", "it italicizes text.");
cooked("_trout_", "<p><em>trout</em></p>", "it italicizes text.");
cooked("***hello***", "<p><strong><em>hello</em></strong></p>", "it can do bold and italics at once.");
cooked("word_with_underscores", "<p>word_with_underscores</p>", "it doesn't do intraword italics");
cooked("common/_special_font_face.html.erb", "<p>common/_special_font_face.html.erb</p>", "it doesn't intraword with a slash");
cooked("hello \\*evil\\*", "<p>hello *evil*</p>", "it supports escaping of asterisks");
cooked("hello \\_evil\\_", "<p>hello _evil_</p>", "it supports escaping of italics");
cooked("brussel sproutes are *awful*.", "<p>brussel sproutes are <em>awful</em>.</p>", "it doesn't swallow periods.");
});
test("Traditional Line Breaks", function() {
var 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");
});
test("Line Breaks", function() {
cooked("[] first choice\n[] second choice",
"<p>[] first choice<br/>[] second choice</p>",
"it handles new lines correctly with [] options");
cooked("<blockquote>evil</blockquote>\ntrout",
"<blockquote>evil</blockquote>\n\n<p>trout</p>",
"it doesn't insert <br> after blockquotes");
cooked("leading<blockquote>evil</blockquote>\ntrout",
"leading<blockquote>evil</blockquote>\n\n<p>trout</p>",
"it doesn't insert <br> after blockquotes with leading text");
});
test("Paragraphs for HTML", function() {
cooked("<div>hello world</div>", "<div>hello world</div>", "it doesn't surround <div> with paragraphs");
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() {
cooked("EvilTrout: http://eviltrout.com",
'<p>EvilTrout: <a href="http://eviltrout.com">http://eviltrout.com</a></p>',
"autolinks a URL");
cooked("Youtube: http://www.youtube.com/watch?v=1MrpeBRkM5A",
'<p>Youtube: <a href="http://www.youtube.com/watch?v=1MrpeBRkM5A">http://www.youtube.com/watch?v=1MrpeBRkM5A</a></p>',
"allows links to contain query params");
cooked("Derpy: http://derp.com?__test=1",
'<p>Derpy: <a href="http://derp.com?__test=1">http://derp.com?__test=1</a></p>',
"works with double underscores in urls");
cooked("Derpy: http://derp.com?_test_=1",
'<p>Derpy: <a href="http://derp.com?_test_=1">http://derp.com?_test_=1</a></p>',
"works with underscores in urls");
cooked("Atwood: www.codinghorror.com",
'<p>Atwood: <a href="http://www.codinghorror.com">www.codinghorror.com</a></p>',
"autolinks something that begins with www");
cooked("Atwood: http://www.codinghorror.com",
'<p>Atwood: <a href="http://www.codinghorror.com">http://www.codinghorror.com</a></p>',
"autolinks a URL with http://www");
cooked("EvilTrout: http://eviltrout.com hello",
'<p>EvilTrout: <a href="http://eviltrout.com">http://eviltrout.com</a> hello</p>',
"autolinks with trailing text");
cooked("here is [an example](http://twitter.com)",
'<p>here is <a href="http://twitter.com">an example</a></p>',
"supports markdown style links");
cooked("Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)",
'<p>Batman: <a href="http://en.wikipedia.org/wiki/The_Dark_Knight_(film)">http://en.wikipedia.org/wiki/The_Dark_Knight_(film)</a></p>',
"autolinks a URL with parentheses (like Wikipedia)");
cooked("Here's a tweet:\nhttps://twitter.com/evil_trout/status/345954894420787200",
"<p>Here's a tweet:<br/><a href=\"https://twitter.com/evil_trout/status/345954894420787200\" class=\"onebox\" target=\"_blank\">https://twitter.com/evil_trout/status/345954894420787200</a></p>",
"It doesn't strip the new line.");
cooked("1. View @eviltrout's profile here: http://meta.discourse.org/users/eviltrout/activity<br/>next line.",
"<ol><li>View <span class=\"mention\">@eviltrout</span>'s profile here: <a href=\"http://meta.discourse.org/users/eviltrout/activity\">http://meta.discourse.org/users/eviltrout/activity</a><br>next line.</li></ol>",
"allows autolinking within a list without inserting a paragraph.");
cooked("[3]: http://eviltrout.com", "", "It doesn't autolink markdown link references");
cooked("http://discourse.org and http://discourse.org/another_url and http://www.imdb.com/name/nm2225369",
"<p><a href=\"http://discourse.org\">http://discourse.org</a> and " +
"<a href=\"http://discourse.org/another_url\">http://discourse.org/another_url</a> and " +
"<a href=\"http://www.imdb.com/name/nm2225369\">http://www.imdb.com/name/nm2225369</a></p>",
'allows multiple links on one line');
cooked("* [Evil Trout][1]\n [1]: http://eviltrout.com",
"<ul><li><a href=\"http://eviltrout.com\">Evil Trout</a></li></ul>",
"allows markdown link references in a list");
cooked("User [MOD]: Hello!",
"<p>User [MOD]: Hello!</p>",
"It does not consider references that are obviously not URLs");
});
test("simple quotes", function() {
cooked("> nice!", "<blockquote><p>nice!</p></blockquote>", "it supports simple quotes");
cooked(" > nice!", "<blockquote><p>nice!</p></blockquote>", "it allows quotes with preceeding spaces");
cooked("> level 1\n> > level 2",
"<blockquote><p>level 1</p><blockquote><p>level 2</p></blockquote></blockquote>",
"it allows nesting of blockquotes");
cooked("> level 1\n> > level 2",
"<blockquote><p>level 1</p><blockquote><p>level 2</p></blockquote></blockquote>",
"it allows nesting of blockquotes with spaces");
cooked("- hello\n\n > world\n > eviltrout",
"<ul><li>hello</li></ul>\n\n<blockquote><p>world<br/>eviltrout</p></blockquote>",
"it allows quotes within a list.");
cooked(" > indent 1\n > indent 2", "<blockquote><p>indent 1<br/>indent 2</p></blockquote>", "allow multiple spaces to indent");
});
test("Quotes", function() {
cookedOptions("[quote=\"eviltrout, post: 1\"]\na quote\n\nsecond line\n\nthird line[/quote]",
{ topicId: 2 },
"<p><aside class=\"quote\" data-post=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>eviltrout said:</div><blockquote>" +
"<p>a quote</p><p>second line</p><p>third line</p></blockquote></aside></p>",
"works with multiple lines");
cookedOptions("1[quote=\"bob, post:1\"]my quote[/quote]2",
{ topicId: 2, lookupAvatar: function(name) { return "" + name; }, sanitize: true },
"<p>1</p>\n\n<p><aside class=\"quote\" data-post=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>bob" +
"bob said:</div><blockquote><p>my quote</p></blockquote></aside></p>\n\n<p>2</p>",
"handles quotes properly");
cookedOptions("1[quote=\"bob, post:1\"]my quote[/quote]2",
{ topicId: 2, lookupAvatar: function(name) { } },
"<p>1</p>\n\n<p><aside class=\"quote\" data-post=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>bob said:" +
"</div><blockquote><p>my quote</p></blockquote></aside></p>\n\n<p>2</p>",
"includes no avatar if none is found");
});
test("Mentions", function() {
var alwaysTrue = { mentionLookup: (function() { return true; }) };
cookedOptions("Hello @sam", alwaysTrue,
"<p>Hello <a class=\"mention\" href=\"/users/sam\">@sam</a></p>",
"translates mentions to links");
cooked("Hello @EvilTrout", "<p>Hello <span class=\"mention\">@EvilTrout</span></p>", "adds a mention class");
cooked("robin@email.host", "<p>robin@email.host</p>", "won't add mention class to an email address");
cooked("hanzo55@yahoo.com", "<p>hanzo55@yahoo.com</p>", "won't be affected by email addresses that have a number before the @ symbol");
cooked("@EvilTrout yo", "<p><span class=\"mention\">@EvilTrout</span> yo</p>", "it handles mentions at the beginning of a string");
cooked("yo\n@EvilTrout", "<p>yo<br/><span class=\"mention\">@EvilTrout</span></p>", "it handles mentions at the beginning of a new line");
cooked("`evil` @EvilTrout `trout`",
"<p><code>evil</code> <span class=\"mention\">@EvilTrout</span> <code>trout</code></p>",
"deals correctly with multiple <code> blocks");
cooked("```\na @test\n```", "<p><pre><code class=\"lang-auto\">a @test</code></pre></p>", "should not do mentions within a code block.");
cooked("> foo bar baz @eviltrout",
"<blockquote><p>foo bar baz <span class=\"mention\">@eviltrout</span></p></blockquote>",
"handles mentions in simple quotes");
cooked("> foo bar baz @eviltrout ohmagerd\nlook at this",
"<blockquote><p>foo bar baz <span class=\"mention\">@eviltrout</span> ohmagerd<br/>look at this</p></blockquote>",
"does mentions properly with trailing text within a simple quote");
cooked("`code` is okay before @mention",
"<p><code>code</code> is okay before <span class=\"mention\">@mention</span></p>",
"Does not mention in an inline code block");
cooked("@mention is okay before `code`",
"<p><span class=\"mention\">@mention</span> is okay before <code>code</code></p>",
"Does not mention in an inline code block");
cooked("don't `@mention`",
"<p>don't <code>@mention</code></p>",
"Does not mention in an inline code block");
cooked("Yes `@this` should be code @eviltrout",
"<p>Yes <code>@this</code> should be code <span class=\"mention\">@eviltrout</span></p>",
"Does not mention in an inline code block");
cooked("@eviltrout and `@eviltrout`",
"<p><span class=\"mention\">@eviltrout</span> and <code>@eviltrout</code></p>",
"you can have a mention in an inline code block following a real mention.");
cooked("1. this is a list\n\n2. this is an @eviltrout mention\n",
"<ol><li><p>this is a list</p></li><li><p>this is an <span class=\"mention\">@eviltrout</span> mention</p></li></ol>",
"it mentions properly in a list.");
cookedOptions("@eviltrout", alwaysTrue,
"<p><a class=\"mention\" href=\"/users/eviltrout\">@eviltrout</a></p>",
"it doesn't onebox mentions");
});
test("Heading", function() {
cooked("**Bold**\n----------",
"<h2><strong>Bold</strong></h2>",
"It will bold the heading");
});
test("Oneboxing", function() {
var matches = function(input, regexp) {
return Discourse.Markdown.cook(input, {mentionLookup: false }).match(regexp);
};
ok(!matches("- http://www.textfiles.com/bbs/MINDVOX/FORUMS/ethics\n\n- http://drupal.org", /onebox/),
"doesn't onebox a link within a list");
ok(matches("http://test.com", /onebox/), "adds a onebox class to a link on its own line");
ok(matches("http://test.com\nhttp://test2.com", /onebox[\s\S]+onebox/m), "supports multiple links");
ok(!matches("http://test.com bob", /onebox/), "doesn't onebox links that have trailing text");
ok(!matches("[Tom Cruise](http://www.tomcruise.com/)", "onebox"), "Markdown links with labels are not oneboxed");
ok(matches("[http://www.tomcruise.com/](http://www.tomcruise.com/)",
"onebox"),
"Markdown links where the label is the same as the url are oneboxed");
cooked("http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street",
"<p><a href=\"http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street\" class=\"onebox\"" +
" target=\"_blank\">http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street</a></p>",
"works with links that have underscores in them");
});
test("Code Blocks", function() {
cooked("```\na\nb\nc\n\nd\n```",
"<p><pre><code class=\"lang-auto\">a\nb\nc\n\nd</code></pre></p>",
"it treats new lines properly");
cooked("```\ntest\n```",
"<p><pre><code class=\"lang-auto\">test</code></pre></p>",
"it supports basic code blocks");
cooked("```json\n{hello: 'world'}\n```\ntrailing",
"<p><pre><code class=\"json\">{hello: &#x27;world&#x27;}</code></pre></p>\n\n<p>trailing</p>",
"It does not truncate text after a code block.");
cooked("```json\nline 1\n\nline 2\n\n\nline3\n```",
"<p><pre><code class=\"json\">line 1\n\nline 2\n\n\nline3</code></pre></p>",
"it maintains new lines inside a code block.");
cooked("hello\nworld\n```json\nline 1\n\nline 2\n\n\nline3\n```",
"<p>hello<br/>world<br/></p>\n\n<p><pre><code class=\"json\">line 1\n\nline 2\n\n\nline3</code></pre></p>",
"it maintains new lines inside a code block with leading content.");
cooked("```text\n<header>hello</header>\n```",
"<p><pre><code class=\"text\">&lt;header&gt;hello&lt;/header&gt;</code></pre></p>",
"it escapes code in the code block");
cooked("```ruby\n# cool\n```",
"<p><pre><code class=\"ruby\"># cool</code></pre></p>",
"it supports changing the language");
cooked(" ```\n hello\n ```",
"<pre><code>&#x60;&#x60;&#x60;\nhello\n&#x60;&#x60;&#x60;</code></pre>",
"only detect ``` at the begining of lines");
cooked("```ruby\ndef self.parse(text)\n\n text\nend\n```",
"<p><pre><code class=\"ruby\">def self.parse(text)\n\n text\nend</code></pre></p>",
"it allows leading spaces on lines in a code block.");
cooked("```ruby\nhello `eviltrout`\n```",
"<p><pre><code class=\"ruby\">hello &#x60;eviltrout&#x60;</code></pre></p>",
"it allows code with backticks in it");
cooked("```eviltrout\nhello\n```",
"<p><pre><code class=\"lang-auto\">hello</code></pre></p>",
"it doesn't not whitelist all classes");
cooked("```[quote=\"sam, post:1, topic:9441, full:true\"]This is `<not>` a bug.[/quote]```",
"<p><pre><code class=\"lang-auto\">[quote=&quot;sam, post:1, topic:9441, full:true&quot;]This is &#x60;&lt;not&gt;&#x60; a bug.[/quote]</code></pre></p>",
"it allows code with backticks in it");
});
test("sanitize", function() {
var sanitize = Discourse.Markdown.sanitize;
equal(sanitize("<i class=\"icon-bug icon-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>");
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("<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");
});
test("URLs in BBCode tags", function() {
cooked("[img]http://eviltrout.com/eviltrout.png[/img][img]http://samsaffron.com/samsaffron.png[/img]",
"<p><img src=\"http://eviltrout.com/eviltrout.png\"/><img src=\"http://samsaffron.com/samsaffron.png\"/></p>",
"images are properly parsed");
cooked("[url]http://discourse.org[/url]",
"<p><a href=\"http://discourse.org\">http://discourse.org</a></p>",
"links are properly parsed");
cooked("[url=http://discourse.org]discourse[/url]",
"<p><a href=\"http://discourse.org\">discourse</a></p>",
"named links are properly parsed");
});
test("urlAllowed", function() {
var allowed = function(url, msg) {
equal(Discourse.Markdown.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");
});

View File

@ -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() {
this.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() {
this.stub(Discourse, "ajax").returns(Ember.RSVP.resolve());
Discourse.Onebox.load(this.anchor, false);
Discourse.Onebox.load(this.anchor, false);
start();
ok(Discourse.ajax.calledOnce);
});

View File

@ -1,71 +0,0 @@
module("Discourse.PreloadStore", {
setup: function() {
PreloadStore.store('bane', 'evil');
}
});
test("get", function() {
blank(PreloadStore.get('joker'), "returns blank for a missing key");
equal(PreloadStore.get('bane'), 'evil', "returns the value for that key");
});
test("remove", function() {
PreloadStore.remove('bane');
blank(PreloadStore.get('bane'), "removes the value if the key exists");
});
asyncTestDiscourse("getAndRemove returns a promise that resolves to null", function() {
expect(1);
PreloadStore.getAndRemove('joker').then(function(result) {
blank(result);
start();
});
});
asyncTestDiscourse("getAndRemove returns a promise that resolves to the result of the finder", function() {
expect(1);
var finder = function() { return 'batdance'; };
PreloadStore.getAndRemove('joker', finder).then(function(result) {
equal(result, 'batdance');
start();
});
});
asyncTestDiscourse("getAndRemove returns a promise that resolves to the result of the finder's promise", function() {
expect(1);
var finder = function() {
return Ember.Deferred.promise(function(promise) { promise.resolve('hahahah'); });
};
PreloadStore.getAndRemove('joker', finder).then(function(result) {
equal(result, 'hahahah');
start();
});
});
asyncTestDiscourse("returns a promise that rejects with the result of the finder's rejected promise", function() {
expect(1);
var finder = function() {
return Ember.Deferred.promise(function(promise) { promise.reject('error'); });
};
PreloadStore.getAndRemove('joker', finder).then(null, function(result) {
equal(result, 'error');
start();
});
});
asyncTestDiscourse("returns a promise that resolves to 'evil'", function() {
expect(1);
PreloadStore.getAndRemove('bane').then(function(result) {
equal(result, 'evil');
start();
});
});

View File

@ -1,147 +0,0 @@
module("Discourse.Utilities");
var utils = Discourse.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");
});
var validUpload = utils.validateUploadedFiles;
test("validateUploadedFiles", function() {
ok(!validUpload(null), "no files are invalid");
ok(!validUpload(undefined), "undefined files are invalid");
ok(!validUpload([]), "empty array of files is invalid");
});
test("uploading one file", function() {
this.stub(bootbox, "alert");
ok(!validUpload([1, 2]));
ok(bootbox.alert.calledWith(I18n.t('post.errors.too_many_uploads')));
});
test("new user cannot upload images", function() {
Discourse.SiteSettings.newuser_max_images = 0;
this.stub(bootbox, "alert");
ok(!validUpload([{name: "image.png"}]));
ok(bootbox.alert.calledWith(I18n.t('post.errors.image_upload_not_allowed_for_new_user')));
});
test("new user cannot upload attachments", function() {
Discourse.SiteSettings.newuser_max_attachments = 0;
this.stub(bootbox, "alert");
ok(!validUpload([{name: "roman.txt"}]));
ok(bootbox.alert.calledWith(I18n.t('post.errors.attachment_upload_not_allowed_for_new_user')));
});
test("ensures an authorized upload", function() {
var html = { name: "unauthorized.html" };
var extensions = Discourse.SiteSettings.authorized_extensions.replace(/\|/g, ", ");
this.stub(bootbox, "alert");
ok(!validUpload([html]));
ok(bootbox.alert.calledWith(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: extensions })));
});
test("prevents files that are too big from being uploaded", function() {
var image = { name: "image.png", size: 10 * 1024 };
Discourse.SiteSettings.max_image_size_kb = 5;
Discourse.User.currentProp("trust_level", 1);
this.stub(bootbox, "alert");
ok(!validUpload([image]));
ok(bootbox.alert.calledWith(I18n.t('post.errors.image_too_large', { max_size_kb: 5 })));
});
var dummyBlob = function() {
var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
if (BlobBuilder) {
var bb = new BlobBuilder();
bb.append([1]);
return bb.getBlob("image/png");
} else {
return new Blob([1], { "type" : "image\/png" });
}
};
test("allows valid uploads to go through", function() {
Discourse.User.currentProp("trust_level", 1);
Discourse.SiteSettings.max_image_size_kb = 15;
this.stub(bootbox, "alert");
// image
var image = { name: "image.png", size: 10 * 1024 };
ok(validUpload([image]));
// pasted image
var pastedImage = dummyBlob();
ok(validUpload([pastedImage]));
ok(!bootbox.alert.calledOnce);
});
var getUploadMarkdown = function(filename) {
return utils.getUploadMarkdown({
original_filename: filename,
filesize: 42,
width: 100,
height: 200,
url: "/uploads/123/abcdef.ext"
});
};
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)');
});
test("isAnImage", function() {
_.each(["png", "jpg", "jpeg", "bmp", "gif", "tif", "tiff"], 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(!utils.isAnImage("file.txt"));
ok(!utils.isAnImage("http://foo.bar/path/to/file.txt"));
ok(!utils.isAnImage(""));
});
test("avatarUrl", function() {
blank(Discourse.Utilities.avatarUrl('', 'tiny'), "no template returns blank");
equal(Discourse.Utilities.avatarUrl('/fake/template/{size}.png', 'tiny'), "/fake/template/20.png", "simple avatar url");
equal(Discourse.Utilities.avatarUrl('/fake/template/{size}.png', 'large'), "/fake/template/45.png", "different size");
});
test("avatarImg", function() {
var avatarTemplate = "/path/to/avatar/{size}.png";
equal(Discourse.Utilities.avatarImg({avatarTemplate: avatarTemplate, size: 'tiny'}),
"<img width='20' height='20' src='/path/to/avatar/20.png' class='avatar'>",
"it returns the avatar html");
equal(Discourse.Utilities.avatarImg({avatarTemplate: avatarTemplate, size: 'tiny', title: 'evilest trout'}),
"<img width='20' height='20' src='/path/to/avatar/20.png' class='avatar' title='evilest trout'>",
"it adds a title if supplied");
equal(Discourse.Utilities.avatarImg({avatarTemplate: avatarTemplate, size: 'tiny', extraClasses: 'evil fish'}),
"<img width='20' height='20' src='/path/to/avatar/20.png' class='avatar evil fish'>",
"it adds extra classes if supplied");
blank(Discourse.Utilities.avatarImg({avatarTemplate: "", size: 'tiny'}),
"it doesn't render avatars for invalid avatar template");
});
module("Discourse.Utilities.cropAvatar with animated avatars", {
setup: function() { Discourse.SiteSettings.allow_animated_avatars = true; }
});
asyncTestDiscourse("cropAvatar", function() {
expect(1);
Discourse.Utilities.cropAvatar("/path/to/avatar.gif", "image/gif").then(function(avatarTemplate) {
equal(avatarTemplate, "/path/to/avatar.gif", "returns the url to the gif when animated gif are enabled");
start();
});
});