diff --git a/Gemfile b/Gemfile index 5d32bfd68fd..c8cb3fd3b79 100644 --- a/Gemfile +++ b/Gemfile @@ -118,6 +118,7 @@ group :test, :development do gem 'rspec-given' gem 'pry-rails' gem 'pry-nav' + gem 'webrick' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index eae4c8a2729..52811943135 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -472,6 +472,7 @@ GEM uglifier (2.0.1) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) + webrick (1.3.1) PLATFORMS ruby @@ -572,3 +573,4 @@ DEPENDENCIES turbo-sprockets-rails3 uglifier vestal_versions! + webrick diff --git a/app/assets/javascripts/discourse/mixins/selected_posts_count.js b/app/assets/javascripts/discourse/mixins/selected_posts_count.js index 38425c9408b..e9e6b5496c1 100644 --- a/app/assets/javascripts/discourse/mixins/selected_posts_count.js +++ b/app/assets/javascripts/discourse/mixins/selected_posts_count.js @@ -9,10 +9,10 @@ Discourse.SelectedPostsCount = Em.Mixin.create({ selectedPostsCount: function() { - if (!this.get('selectedPosts')) return 0; - if (this.get('allPostsSelected')) return this.get('posts_count') || this.get('topic.posts_count'); + if (!this.get('selectedPosts')) return 0; + return this.get('selectedPosts.length'); }.property('selectedPosts.length', 'allPostsSelected') diff --git a/app/assets/javascripts/discourse/models/post.js b/app/assets/javascripts/discourse/models/post.js index 2812412f963..622d19cf3f3 100644 --- a/app/assets/javascripts/discourse/models/post.js +++ b/app/assets/javascripts/discourse/models/post.js @@ -293,7 +293,7 @@ Discourse.Post.reopenClass({ create: function(obj, topic) { var result = this._super(obj); this.createActionSummary(result); - if (obj.reply_to_user) { + if (obj && obj.reply_to_user) { result.set('reply_to_user', Discourse.User.create(obj.reply_to_user)); } result.set('topic', topic); diff --git a/lib/discourse_iife.rb b/lib/discourse_iife.rb index 30e8f7bde96..867c11b947a 100644 --- a/lib/discourse_iife.rb +++ b/lib/discourse_iife.rb @@ -6,9 +6,12 @@ class DiscourseIIFE < Sprockets::Processor path = context.pathname.to_s # Only discourse or admin paths - return data unless (path =~ /\/javascripts\/discourse/ || path =~ /\/javascripts\/admin/) + return data unless (path =~ /\/javascripts\/discourse/ || path =~ /\/javascripts\/admin/ || path =~ /\/test\/javascripts/) - # Ugh, ignore translations + # Ignore the js helper + return data if (path =~ /test\_helper\.js/) + + # Ignore translations return data if (path =~ /\/translations/) # We don't add IIFEs to handlebars diff --git a/lib/tasks/qunit.rake b/lib/tasks/qunit.rake new file mode 100644 index 00000000000..b75296a667a --- /dev/null +++ b/lib/tasks/qunit.rake @@ -0,0 +1,48 @@ +desc "Runs the qunit test suite" + +task "qunit:test" => :environment do + + require "rack" + require "webrick" + + unless %x{which phantomjs > /dev/null 2>&1} + abort "PhantomJS is not installed. Download from http://phantomjs.org" + end + + port = ENV['TEST_SERVER_PORT'] || 60099 + server = Thread.new do + Rack::Server.start(:config => "config.ru", + :Logger => WEBrick::Log.new("/dev/null"), + :AccessLog => [], + :Port => port) + end + + begin + success = true + test_path = "#{Rails.root}/vendor/assets/javascripts" + cmd = "phantomjs #{test_path}/run-qunit.js \"http://localhost:#{port}/qunit\"" + + rake_system(cmd) + + # A bit of a hack until we can figure this out on Travis + tries = 0 + while tries < 3 && $?.exitstatus === 124 + tries += 1 + puts "\nTimed Out. Trying again...\n" + sh(cmd) + end + + success &&= $?.success? + + ensure + server.kill + end + + if success + puts "\nTests Passed" + else + puts "\nTests Failed" + exit(1) + end + +end \ No newline at end of file diff --git a/spec/javascripts/components/bbcode_spec.js b/spec/javascripts/components/bbcode_spec.js deleted file mode 100644 index db2c5a57e0a..00000000000 --- a/spec/javascripts/components/bbcode_spec.js +++ /dev/null @@ -1,87 +0,0 @@ -/*global waitsFor:true expect:true describe:true beforeEach:true it:true md5:true */ - -describe("Discourse.BBCode", function() { - - var format = Discourse.BBCode.format; - - describe("quoting", function() { - - // Format text without an avatar lookup - function formatQuote(text) { - return format(text, {lookupAvatar: false}); - } - - it("can quote", function() { - expect(formatQuote("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]")). - toBe("
\n"); - }); - - it("can nest quotes", function() { - expect(formatQuote("[quote=\"eviltrout, post:1, topic:1\"]abc[quote=\"eviltrout, post:2, topic:2\"]nested[/quote][/quote]")). - toBe("
\n"); - }); - - it("can handle more than one quote", function() { - expect(formatQuote("before[quote=\"eviltrout, post:1, topic:1\"]first[/quote]middle[quote=\"eviltrout, post:2, topic:2\"]second[/quote]after")). - toBe("before
first\n
middle
second\n
after"); - }); - - describe("extractQuotes", function() { - - var extractQuotes = Discourse.BBCode.extractQuotes; - - it("returns an object a template renderer", function() { - var q = "[quote=\"eviltrout, post:1, topic:2\"]hello[/quote]"; - var result = extractQuotes(q + " world"); - - expect(result.text).toBe(md5(q) + "\n world"); - expect(result.template).not.toBe(null); - }); - - }); - - describe("buildQuoteBBCode", function() { - - var build = Discourse.BBCode.buildQuoteBBCode; - - var post = Discourse.Post.create({ - cooked: "
lorem ipsum
", - username: "eviltrout", - post_number: 1, - topic_id: 2 - }); - - it("returns an empty string when contents is undefined", function() { - expect(build(post, undefined)).toBe(""); - expect(build(post, null)).toBe(""); - expect(build(post, "")).toBe(""); - }); - - it("returns the quoted contents", function() { - expect(build(post, "lorem")).toBe("[quote=\"eviltrout, post:1, topic:2\"]\nlorem\n[/quote]\n\n"); - }); - - it("trims white spaces before & after the quoted contents", function() { - expect(build(post, " lorem ")).toBe("[quote=\"eviltrout, post:1, topic:2\"]\nlorem\n[/quote]\n\n"); - }); - - it("marks quotes as full when the quote is the full message", function() { - expect(build(post, "lorem ipsum")).toBe("[quote=\"eviltrout, post:1, topic:2, full:true\"]\nlorem ipsum\n[/quote]\n\n"); - }); - - it("keeps BBCode formatting", function() { - expect(build(post, "**lorem** ipsum")).toBe("[quote=\"eviltrout, post:1, topic:2, full:true\"]\n**lorem** ipsum\n[/quote]\n\n"); - }); - - }); - - }); - -}); diff --git a/test/javascripts/components/bbcode_test.js b/test/javascripts/components/bbcode_test.js index e0fef9c33dc..ee8253329c0 100644 --- a/test/javascripts/components/bbcode_test.js +++ b/test/javascripts/components/bbcode_test.js @@ -1,9 +1,9 @@ -/*global module:true test:true ok:true visit:true expect:true exists:true count:true equal:true */ +/*global module:true test:true ok:true visit:true expect:true exists:true count:true equal:true present:true md5:true */ module("Discourse.BBCode"); var format = function(input, expected, text) { - equal(Discourse.BBCode.format(input), expected, text); + equal(Discourse.BBCode.format(input, {lookupAvatar: false}), expected, text); } test('basic bbcode', function() { @@ -36,4 +36,74 @@ test('tags with arguments', function() { format("[email=eviltrout@mailinator.com]evil trout[/email]", "evil trout", "supports [email] with a title"); format("[u][i]abc[/i][/u]", "abc", "can nest tags"); format("[b]first[/b] [b]second[/b]", "first second", "can bold two things on the same line"); -}); \ No newline at end of file +}); + + +test("quotes", function() { + + var post = Discourse.Post.create({ + cooked: "lorem ipsum
", + username: "eviltrout", + post_number: 1, + topic_id: 2 + }); + + var formatQuote = function(val, expected, text) { + equal(Discourse.BBCode.buildQuoteBBCode(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"); + +}); + +test("quote formatting", function() { + + // TODO: This HTML matching is quite ugly. + format("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]", + "abc\n
", + "renders quotes properly"); + + format("[quote=\"eviltrout, post:1, topic:1\"]abc[quote=\"eviltrout, post:2, topic:2\"]nested[/quote][/quote]", + "
abc\n\n \n\n \n \n eviltrout\n said:\n\nnested\n
", + "can nest quotes"); + + format("before[quote=\"eviltrout, post:1, topic:1\"]first[/quote]middle[quote=\"eviltrout, post:2, topic:2\"]second[/quote]after", + "before
first\n
middle
second\n
after",
+ "can handle more than one quote");
+
+});
+
+
+test("extract quotes", function() {
+
+ var q = "[quote=\"eviltrout, post:1, topic:2\"]hello[/quote]";
+ var result = Discourse.BBCode.extractQuotes(q + " world");
+
+ equal(result.text, md5(q) + "\n world");
+ present(result.template);
+
+});
+
diff --git a/test/javascripts/mixins/presence_test.js b/test/javascripts/mixins/presence_test.js
index f6e0816b00e..abaf493c0ee 100644
--- a/test/javascripts/mixins/presence_test.js
+++ b/test/javascripts/mixins/presence_test.js
@@ -7,7 +7,7 @@ var testObj = Em.Object.createWithMixins(Discourse.Presence, {
nonEmptyString: "Evil Trout",
emptyArray: [],
nonEmptyArray: [1, 2, 3],
- age: 34,
+ age: 34
});
test("present", function() {
@@ -18,7 +18,6 @@ test("present", function() {
ok(testObj.present('age'), "integers are present");
});
-
test("blank", function() {
ok(testObj.blank('emptyString'), "Empty strings are blank");
ok(!testObj.blank('nonEmptyString'), "Non empty strings are not blank");
diff --git a/test/javascripts/mixins/selected_posts_count_test.js b/test/javascripts/mixins/selected_posts_count_test.js
new file mode 100644
index 00000000000..b6450b4adad
--- /dev/null
+++ b/test/javascripts/mixins/selected_posts_count_test.js
@@ -0,0 +1,34 @@
+/*global module:true test:true ok:true visit:true expect:true exists:true count:true equal:true */
+module("Discourse.SelectedPostsCount");
+
+var buildTestObj = function(params, topicParams) {
+ return Ember.Object.createWithMixins(Discourse.SelectedPostsCount, params || {});
+};
+
+test("without selectedPosts", function () {
+ var testObj = buildTestObj();
+
+ equal(testObj.get('selectedPostsCount'), 0, "No posts are selected without a selectedPosts property");
+
+ testObj.set('selectedPosts', []);
+ equal(testObj.get('selectedPostsCount'), 0, "No posts are selected when selectedPosts is an empty array");
+});
+
+test("with some selectedPosts", function() {
+ var testObj = buildTestObj({ selectedPosts: [Discourse.Post.create()] });
+ equal(testObj.get('selectedPostsCount'), 1, "It returns the amount of posts");
+});
+
+test("when all posts are selected and there is a posts_count", function() {
+ var testObj = buildTestObj({ allPostsSelected: true, posts_count: 1024 });
+ equal(testObj.get('selectedPostsCount'), 1024, "It returns the posts_count");
+});
+
+test("when all posts are selected and there is topic with a posts_count", function() {
+ var testObj = buildTestObj({
+ allPostsSelected: true,
+ topic: Discourse.Topic.create({ posts_count: 3456 })
+ });
+
+ equal(testObj.get('selectedPostsCount'), 3456, "It returns the topic's posts_count");
+});
diff --git a/vendor/assets/javascripts/run-qunit.js b/vendor/assets/javascripts/run-qunit.js
new file mode 100644
index 00000000000..469c7705919
--- /dev/null
+++ b/vendor/assets/javascripts/run-qunit.js
@@ -0,0 +1,122 @@
+// PhantomJS QUnit Test Runner
+
+/*globals QUnit phantom*/
+
+var args = phantom.args;
+if (args.length < 1 || args.length > 2) {
+ console.log("Usage: " + phantom.scriptName + "