From f157ec1f91f9f28f0b8bb23367bd358a09b06df9 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 4 Sep 2013 11:53:00 -0400 Subject: [PATCH] Select +Replies for bulk operations --- .../controllers/merge_topic_controller.js | 8 +- .../controllers/split_topic_controller.js | 17 +-- .../discourse/controllers/topic_controller.js | 74 +++++++++-- .../discourse/mixins/selected_posts_count.js | 11 +- .../javascripts/discourse/models/post.js | 10 +- .../discourse/templates/post.js.handlebars | 5 +- .../javascripts/discourse/views/post_view.js | 13 +- .../application/topic-post.css.scss | 33 ++--- app/controllers/application_controller.rb | 10 ++ app/controllers/posts_controller.rb | 3 +- app/controllers/topics_controller.rb | 6 +- config/jshint.yml | 1 + config/locales/client.en.yml | 1 + spec/controllers/posts_controller_spec.rb | 17 ++- spec/controllers/topics_controller_spec.rb | 21 ++++ .../avatar_selector_controller_test.js | 2 +- .../controllers/flag_controller_test.js | 6 +- .../controllers/topic_controller_test.js | 116 +++++++++++++++--- test/javascripts/helpers/qunit_helpers.js | 4 + test/javascripts/jshint_all.js.erb | 1 + 20 files changed, 282 insertions(+), 77 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/merge_topic_controller.js b/app/assets/javascripts/discourse/controllers/merge_topic_controller.js index 9d79f00c3..672654877 100644 --- a/app/assets/javascripts/discourse/controllers/merge_topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/merge_topic_controller.js @@ -12,6 +12,7 @@ Discourse.MergeTopicController = Discourse.ObjectController.extend(Discourse.Sel topicController: Em.computed.alias('controllers.topic'), selectedPosts: Em.computed.alias('topicController.selectedPosts'), + selectedReplies: Em.computed.alias('topicController.selectedReplies'), allPostsSelected: Em.computed.alias('topicController.allPostsSelected'), buttonDisabled: function() { @@ -31,10 +32,13 @@ Discourse.MergeTopicController = Discourse.ObjectController.extend(Discourse.Sel if (this.get('allPostsSelected')) { promise = Discourse.Topic.mergeTopic(this.get('id'), this.get('selectedTopicId')); } else { - var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }); + var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }), + replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }); + promise = Discourse.Topic.movePosts(this.get('id'), { destination_topic_id: this.get('selectedTopicId'), - post_ids: postIds + post_ids: postIds, + reply_post_ids: replyPostIds }); } diff --git a/app/assets/javascripts/discourse/controllers/split_topic_controller.js b/app/assets/javascripts/discourse/controllers/split_topic_controller.js index b07b007b0..7d2dd5993 100644 --- a/app/assets/javascripts/discourse/controllers/split_topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/split_topic_controller.js @@ -12,6 +12,7 @@ Discourse.SplitTopicController = Discourse.ObjectController.extend(Discourse.Sel topicController: Em.computed.alias('controllers.topic'), selectedPosts: Em.computed.alias('topicController.selectedPosts'), + selectedReplies: Em.computed.alias('topicController.selectedReplies'), buttonDisabled: function() { if (this.get('saving')) return true; @@ -30,21 +31,23 @@ Discourse.SplitTopicController = Discourse.ObjectController.extend(Discourse.Sel movePostsToNewTopic: function() { this.set('saving', true); - var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }); - var splitTopicController = this; + var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }), + replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }), + self = this; Discourse.Topic.movePosts(this.get('id'), { title: this.get('topicName'), - post_ids: postIds + post_ids: postIds, + reply_post_ids: replyPostIds }).then(function(result) { // Posts moved - splitTopicController.send('closeModal'); - splitTopicController.get('topicController').toggleMultiSelect(); + self.send('closeModal'); + self.get('topicController').toggleMultiSelect(); Em.run.next(function() { Discourse.URL.routeTo(result.url); }); }, function() { // Error moving posts - splitTopicController.flash(I18n.t('topic.split_topic.error')); - splitTopicController.set('saving', false); + self.flash(I18n.t('topic.split_topic.error')); + self.set('saving', false); }); return false; } diff --git a/app/assets/javascripts/discourse/controllers/topic_controller.js b/app/assets/javascripts/discourse/controllers/topic_controller.js index 6dc6c23c6..a1c969011 100644 --- a/app/assets/javascripts/discourse/controllers/topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/topic_controller.js @@ -11,8 +11,15 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected summaryCollapsed: true, needs: ['header', 'modal', 'composer', 'quoteButton'], allPostsSelected: false, - selectedPosts: new Em.Set(), editingTopic: false, + selectedPosts: null, + selectedReplies: null, + + init: function() { + this._super(); + this.set('selectedPosts', new Em.Set()); + this.set('selectedReplies', new Em.Set()); + }, jumpTopDisabled: function() { return (this.get('progressPosition') === 1); @@ -82,18 +89,49 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected return false; }.property('postStream.loaded', 'currentPost', 'postStream.filteredPostsCount'), - selectPost: function(post) { + deselectPost: function(post) { + this.get('selectedPosts').removeObject(post); + + var selectedReplies = this.get('selectedReplies'); + selectedReplies.removeObject(post); + + var selectedReply = selectedReplies.findProperty('post_number', post.get('reply_to_post_number')); + if (selectedReply) { selectedReplies.removeObject(selectedReply); } + + this.set('allPostsSelected', false); + }, + + postSelected: function(post) { + if (this.get('allPostsSelected')) { return true; } + if (this.get('selectedPosts').contains(post)) { return true; } + + if (this.get('selectedReplies').findProperty('post_number', post.get('reply_to_post_number'))) { return true; } + + return false; + }, + + toggledSelectedPost: function(post) { var selectedPosts = this.get('selectedPosts'); - if (selectedPosts.contains(post)) { - selectedPosts.removeObject(post); - this.set('allPostsSelected', false); + if (this.postSelected(post)) { + this.deselectPost(post); + return false; } else { selectedPosts.addObject(post); // If the user manually selects all posts, all posts are selected if (selectedPosts.length === this.get('posts_count')) { - this.set('allPostsSelected'); + this.set('allPostsSelected', true); } + return true; + } + }, + + toggledSelectedPostReplies: function(post) { + var selectedReplies = this.get('selectedReplies'); + if (this.toggledSelectedPost(post)) { + selectedReplies.addObject(post); + } else { + selectedReplies.removeObject(post); } }, @@ -108,6 +146,7 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected deselectAll: function() { this.get('selectedPosts').clear(); + this.get('selectedReplies').clear(); this.set('allPostsSelected', false); }, @@ -177,19 +216,28 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected }, deleteSelected: function() { - var topicController = this; + var self = this; bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) { if (result) { // If all posts are selected, it's the same thing as deleting the topic - if (topicController.get('allPostsSelected')) { - return topicController.deleteTopic(); + if (self.get('allPostsSelected')) { + return self.deleteTopic(); } - var selectedPosts = topicController.get('selectedPosts'); - Discourse.Post.deleteMany(selectedPosts); - topicController.get('model.postStream').removePosts(selectedPosts); - topicController.toggleMultiSelect(); + var selectedPosts = self.get('selectedPosts'), + selectedReplies = self.get('selectedReplies'), + postStream = self.get('postStream'), + toRemove = new Ember.Set(); + + + Discourse.Post.deleteMany(selectedPosts, selectedReplies); + postStream.get('posts').forEach(function (p) { + if (self.postSelected(p)) { toRemove.addObject(p); } + }); + + postStream.removePosts(toRemove); + self.toggleMultiSelect(); } }); }, diff --git a/app/assets/javascripts/discourse/mixins/selected_posts_count.js b/app/assets/javascripts/discourse/mixins/selected_posts_count.js index e9e6b5496..e30cfdf40 100644 --- a/app/assets/javascripts/discourse/mixins/selected_posts_count.js +++ b/app/assets/javascripts/discourse/mixins/selected_posts_count.js @@ -11,10 +11,15 @@ Discourse.SelectedPostsCount = Em.Mixin.create({ selectedPostsCount: function() { if (this.get('allPostsSelected')) return this.get('posts_count') || this.get('topic.posts_count'); - if (!this.get('selectedPosts')) return 0; + var sum = this.get('selectedPosts.length') || 0; + if (this.get('selectedReplies')) { + this.get('selectedReplies').forEach(function (p) { + sum += p.get('reply_count') || 0; + }); + } - return this.get('selectedPosts.length'); - }.property('selectedPosts.length', 'allPostsSelected') + return sum; + }.property('selectedPosts.length', 'allPostsSelected', 'selectedReplies.length') }); diff --git a/app/assets/javascripts/discourse/models/post.js b/app/assets/javascripts/discourse/models/post.js index ab1e088d2..7e6f56560 100644 --- a/app/assets/javascripts/discourse/models/post.js +++ b/app/assets/javascripts/discourse/models/post.js @@ -328,8 +328,7 @@ Discourse.Post = Discourse.Model.extend({ // Whether to show replies directly below showRepliesBelow: function() { - var reply_count, topic; - reply_count = this.get('reply_count'); + var reply_count = this.get('reply_count'); // We don't show replies if there aren't any if (reply_count === 0) return false; @@ -341,7 +340,7 @@ Discourse.Post = Discourse.Model.extend({ if (reply_count > 1) return true; // If we have *exactly* one reply, we have to consider if it's directly below us - topic = this.get('topic'); + var topic = this.get('topic'); return !topic.isReplyDirectlyBelow(this); }.property('reply_count'), @@ -377,11 +376,12 @@ Discourse.Post.reopenClass({ return result; }, - deleteMany: function(posts) { + deleteMany: function(selectedPosts, selectedReplies) { return Discourse.ajax("/posts/destroy_many", { type: 'DELETE', data: { - post_ids: posts.map(function(p) { return p.get('id'); }) + post_ids: selectedPosts.map(function(p) { return p.get('id'); }), + reply_post_ids: selectedReplies.map(function(p) { return p.get('id'); }) } }); }, diff --git a/app/assets/javascripts/discourse/templates/post.js.handlebars b/app/assets/javascripts/discourse/templates/post.js.handlebars index 7817cd39a..7077f869d 100644 --- a/app/assets/javascripts/discourse/templates/post.js.handlebars +++ b/app/assets/javascripts/discourse/templates/post.js.handlebars @@ -32,7 +32,10 @@
- +
+ + +
{{#unless controller.multiSelect}} diff --git a/app/assets/javascripts/discourse/views/post_view.js b/app/assets/javascripts/discourse/views/post_view.js index 272266e7f..466c388ee 100644 --- a/app/assets/javascripts/discourse/views/post_view.js +++ b/app/assets/javascripts/discourse/views/post_view.js @@ -29,17 +29,20 @@ Discourse.PostView = Discourse.GroupedView.extend({ mouseUp: function(e) { if (this.get('controller.multiSelect') && (e.metaKey || e.ctrlKey)) { - this.get('controller').selectPost(this.get('post')); + this.get('controller').toggledSelectedPost(this.get('post')); } }, selected: function() { - var selectedPosts = this.get('controller.selectedPosts'); - if (!selectedPosts) return false; - return selectedPosts.contains(this.get('post')); + return this.get('controller').postSelected(this.get('post')); }.property('controller.selectedPostsCount'), - selectText: function() { + canSelectReplies: function() { + if (this.get('post.reply_count') === 0) { return false; } + return !this.get('selected'); + }.property('post.reply_count', 'selected'), + + selectPostText: function() { return this.get('selected') ? I18n.t('topic.multi_select.selected', { count: this.get('controller.selectedPostsCount') }) : I18n.t('topic.multi_select.select'); }.property('selected', 'controller.selectedPostsCount'), diff --git a/app/assets/stylesheets/application/topic-post.css.scss b/app/assets/stylesheets/application/topic-post.css.scss index 901f23ae0..53530595c 100644 --- a/app/assets/stylesheets/application/topic-post.css.scss +++ b/app/assets/stylesheets/application/topic-post.css.scss @@ -500,9 +500,11 @@ } &.selected { article.boxed { - .post-select { - background-color: $blue; - color: $white; + .select-posts { + button.select-post { + background-color: $blue; + color: $white; + } } .topic-body { .contents { @@ -519,20 +521,23 @@ font-size: 16px; line-height: 20px; - .post-select { - @include border-radius-all(4px); - background-color: $light_gray; - border-top: 1px solid $white; - border-left: 1px solid $white; - border-bottom: 1px solid $gray; - border-right: 1px solid $gray; - color: $darkish_gray; - top: 4px; + .select-posts { position: absolute; right: 5px; - font-size: 12px; - padding: 2px 5px; z-index: 490; + top: 4px; + + button { + @include border-radius-all(4px); + background-color: $light_gray; + border-top: 1px solid $white; + border-left: 1px solid $white; + border-bottom: 1px solid $gray; + border-right: 1px solid $gray; + color: $darkish_gray; + font-size: 12px; + padding: 2px 5px; + } } img { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a9b6abbc9..da27cd283 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -191,6 +191,16 @@ class ApplicationController < ActionController::Base user end + def post_ids_including_replies + post_ids = params[:post_ids].map {|p| p.to_i} + if params[:reply_post_ids] + post_ids << PostReply.where(post_id: params[:reply_post_ids].map {|p| p.to_i}).pluck(:reply_id) + post_ids.flatten! + post_ids.uniq! + end + post_ids + end + private def preload_anonymous_data diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 6a990875e..c3748b7a5 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -150,10 +150,11 @@ class PostsController < ApplicationController params.require(:post_ids) - posts = Post.where(id: params[:post_ids]) + posts = Post.where(id: post_ids_including_replies) raise Discourse::InvalidParameters.new(:post_ids) if posts.blank? # Make sure we can delete the posts + posts.each {|p| guardian.ensure_can_delete!(p) } Post.transaction do diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index c2acad8b7..2005e42f1 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -244,7 +244,7 @@ class TopicsController < ApplicationController topic = Topic.where(id: params[:topic_id]).first guardian.ensure_can_move_posts!(topic) - dest_topic = move_post_to_destination(topic) + dest_topic = move_posts_to_destination(topic) render_topic_changes(dest_topic) end @@ -333,12 +333,12 @@ class TopicsController < ApplicationController private - def move_post_to_destination(topic) + def move_posts_to_destination(topic) args = {} args[:title] = params[:title] if params[:title].present? args[:destination_topic_id] = params[:destination_topic_id].to_i if params[:destination_topic_id].present? - topic.move_posts(current_user, params[:post_ids].map {|p| p.to_i}, args) + topic.move_posts(current_user, post_ids_including_replies, args) end end diff --git a/config/jshint.yml b/config/jshint.yml index 7da52be34..588a527d8 100644 --- a/config/jshint.yml +++ b/config/jshint.yml @@ -92,6 +92,7 @@ predef: - find - sinon - controllerFor + - testController - Favcount browser: true # true if the standard browser globals should be predefined diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index dc26b2cd6..1f7bdadb1 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -763,6 +763,7 @@ en: multi_select: select: 'select' selected: 'selected ({{count}})' + select_replies: 'select +replies' delete: delete selected cancel: cancel selecting description: diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index 578dafd89..70e5d401d 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -163,10 +163,10 @@ describe PostsController do let!(:poster) { log_in(:moderator) } let!(:post1) { Fabricate(:post, user: poster, post_number: 2) } - let!(:post2) { Fabricate(:post, topic_id: post1.topic_id, user: poster, post_number: 3) } + let!(:post2) { Fabricate(:post, topic_id: post1.topic_id, user: poster, post_number: 3, reply_to_post_number: post1.post_number) } it "raises invalid parameters no post_ids" do - lambda { xhr :delete, :destroy_many }.should raise_error(ActionController::ParameterMissing) + lambda { xhr :delete, :destroy_many }.should raise_error(ActionController::ParameterMissing) end it "raises invalid parameters with missing ids" do @@ -189,6 +189,19 @@ describe PostsController do xhr :delete, :destroy_many, post_ids: [post1.id, post2.id] end + describe "can delete replies" do + + before do + PostReply.create(post_id: post1.id, reply_id: post2.id) + end + + it "deletes the post and the reply to it" do + Post.any_instance.expects(:destroy).twice + xhr :delete, :destroy_many, post_ids: [post1.id], reply_post_ids: [post1.id] + end + + end + end end diff --git a/spec/controllers/topics_controller_spec.rb b/spec/controllers/topics_controller_spec.rb index 787acb756..fb2c61ee3 100644 --- a/spec/controllers/topics_controller_spec.rb +++ b/spec/controllers/topics_controller_spec.rb @@ -93,6 +93,27 @@ describe TopicsController do end end + describe "moving replied posts" do + let!(:user) { log_in(:moderator) } + let!(:p1) { Fabricate(:post, user: user) } + let!(:topic) { p1.topic } + let!(:p2) { Fabricate(:post, topic: topic, user: user, reply_to_post_number: p1.post_number ) } + + context 'success' do + + before do + PostReply.create(post_id: p1.id, reply_id: p2.id) + end + + it "moves the child posts too" do + Topic.any_instance.expects(:move_posts).with(user, [p1.id, p2.id], title: 'blah').returns(topic) + xhr :post, :move_posts, topic_id: topic.id, title: 'blah', post_ids: [p1.id], reply_post_ids: [p1.id] + end + end + + end + + describe 'moving to an existing topic' do let!(:user) { log_in(:moderator) } let(:p1) { Fabricate(:post, user: user) } diff --git a/test/javascripts/controllers/avatar_selector_controller_test.js b/test/javascripts/controllers/avatar_selector_controller_test.js index 7fd84a30f..9bcc6ad17 100644 --- a/test/javascripts/controllers/avatar_selector_controller_test.js +++ b/test/javascripts/controllers/avatar_selector_controller_test.js @@ -7,7 +7,7 @@ var avatarSelector = Em.Object.create({ module("Discourse.AvatarSelectorController"); test("avatarTemplate", function() { - var avatarSelectorController = controllerFor("avatarSelector"); + var avatarSelectorController = testController(Discourse.AvatarSelectorController); avatarSelectorController.setProperties(avatarSelector); equal(avatarSelectorController.get("avatarTemplate"), diff --git a/test/javascripts/controllers/flag_controller_test.js b/test/javascripts/controllers/flag_controller_test.js index a8bbc23dd..57840c567 100644 --- a/test/javascripts/controllers/flag_controller_test.js +++ b/test/javascripts/controllers/flag_controller_test.js @@ -16,7 +16,7 @@ var buildAdminUser = function(args) { module("Discourse.FlagController canDeleteSpammer"); test("canDeleteSpammer not staff", function(){ - var flagController = controllerFor('flag', buildPost()); + var flagController = testController(Discourse.FlagController, buildPost()); this.stub(Discourse.User, 'currentProp').withArgs('staff').returns(false); flagController.set('selected', Discourse.PostActionType.create({name_key: 'spam'})); equal(flagController.get('canDeleteSpammer'), false, 'false if current user is not staff'); @@ -29,7 +29,7 @@ var canDeleteSpammer = function(test, postActionType, expected, testName) { test("canDeleteSpammer spam not selected", function(){ this.stub(Discourse.User, 'currentProp').withArgs('staff').returns(true); - this.flagController = controllerFor('flag', buildPost()); + this.flagController = testController(Discourse.FlagController, buildPost()); this.flagController.set('userDetails', buildAdminUser({can_delete_all_posts: true, can_be_deleted: true})); canDeleteSpammer(this, 'off_topic', false, 'false if current user is staff, but selected is off_topic'); canDeleteSpammer(this, 'inappropriate', false, 'false if current user is staff, but selected is inappropriate'); @@ -39,7 +39,7 @@ test("canDeleteSpammer spam not selected", function(){ test("canDeleteSpammer spam selected", function(){ this.stub(Discourse.User, 'currentProp').withArgs('staff').returns(true); - this.flagController = controllerFor('flag', buildPost()); + this.flagController = testController(Discourse.FlagController, buildPost()); this.flagController.set('userDetails', buildAdminUser({can_delete_all_posts: true, can_be_deleted: true})); canDeleteSpammer(this, 'spam', true, 'true if current user is staff, selected is spam, posts and user can be deleted'); diff --git a/test/javascripts/controllers/topic_controller_test.js b/test/javascripts/controllers/topic_controller_test.js index 950b861f1..f45338c24 100644 --- a/test/javascripts/controllers/topic_controller_test.js +++ b/test/javascripts/controllers/topic_controller_test.js @@ -1,22 +1,19 @@ +module("Discourse.TopicController"); -var topic = Discourse.Topic.create({ - title: "Qunit Test Topic", - participants: [ - {id: 1234, - post_count: 4, - username: "eviltrout"} - ] -}); - - -module("Discourse.TopicController", { - setup: function() { - this.topicController = controllerFor('topic', topic); - } -}); +var buildTopic = function() { + return Discourse.Topic.create({ + title: "Qunit Test Topic", + participants: [ + {id: 1234, + post_count: 4, + username: "eviltrout"} + ] + }); +}; test("editingMode", function() { - var topicController = this.topicController; + var topic = buildTopic(), + topicController = testController(Discourse.TopicController, topic); ok(!topicController.get('editingTopic'), "we are not editing by default"); @@ -32,4 +29,89 @@ test("editingMode", function() { topicController.cancelEditingTopic(); ok(!topicController.get('editingTopic'), "cancelling edit mode reverts the property value"); -}); \ No newline at end of file +}); + +test("toggledSelectedPost", function() { + var tc = testController(Discourse.TopicController, buildTopic()), + post = Discourse.Post.create({id: 123, post_number: 2}), + postStream = tc.get('postStream'); + + postStream.appendPost(post); + postStream.appendPost(Discourse.Post.create({id: 124, post_number: 3})); + + blank(tc.get('selectedPosts'), "there are no selected posts by default"); + equal(tc.get('selectedPostsCount'), 0, "there is a selected post count of 0"); + ok(!tc.postSelected(post), "the post is not selected by default"); + + tc.toggledSelectedPost(post); + present(tc.get('selectedPosts'), "there is a selectedPosts collection"); + equal(tc.get('selectedPostsCount'), 1, "there is a selected post now"); + ok(tc.postSelected(post), "the post is now selected"); + + tc.toggledSelectedPost(post); + ok(!tc.postSelected(post), "the post is no longer selected"); + +}); + +test("selectAll", function() { + var tc = testController(Discourse.TopicController, buildTopic()), + post = Discourse.Post.create({id: 123, post_number: 2}), + postStream = tc.get('postStream'); + + postStream.appendPost(post); + + ok(!tc.postSelected(post), "the post is not selected by default"); + tc.selectAll(); + ok(tc.postSelected(post), "the post is now selected"); + ok(tc.get('allPostsSelected'), "all posts are selected"); + tc.deselectAll(); + ok(!tc.postSelected(post), "the post is deselected again"); + ok(!tc.get('allPostsSelected'), "all posts are not selected"); + +}); + +test("Automating setting of allPostsSelected", function() { + var topic = buildTopic(), + tc = testController(Discourse.TopicController, topic), + post = Discourse.Post.create({id: 123, post_number: 2}), + postStream = tc.get('postStream'); + + topic.set('posts_count', 1); + postStream.appendPost(post); + ok(!tc.get('allPostsSelected'), "all posts are not selected by default"); + + tc.toggledSelectedPost(post); + ok(tc.get('allPostsSelected'), "all posts are selected if we select the only post"); + + tc.toggledSelectedPost(post); + ok(!tc.get('allPostsSelected'), "the posts are no longer automatically selected"); +}); + +test("Select Replies when present", function() { + var topic = buildTopic(), + tc = testController(Discourse.TopicController, topic), + p1 = Discourse.Post.create({id: 1, post_number: 1, reply_count: 1}), + p2 = Discourse.Post.create({id: 2, post_number: 2}), + p3 = Discourse.Post.create({id: 2, post_number: 3, reply_to_post_number: 1}), + postStream = tc.get('postStream'); + + ok(!tc.postSelected(p3), "replies are not selected by default"); + tc.toggledSelectedPostReplies(p1); + ok(tc.postSelected(p1), "it selects the post"); + ok(!tc.postSelected(p2), "it doesn't select a post that's not a reply"); + ok(tc.postSelected(p3), "it selects a post that is a reply"); + equal(tc.get('selectedPostsCount'), 2, "it has a selected posts count of two"); + + // If we deselected the post whose replies are selected... + tc.toggledSelectedPost(p1); + ok(!tc.postSelected(p1), "it deselects the post"); + ok(!tc.postSelected(p3), "it deselects the replies too"); + + // If we deselect a reply, it should deselect the parent's replies selected attribute. Weird but what else would make sense? + tc.toggledSelectedPostReplies(p1); + tc.toggledSelectedPost(p3); + ok(tc.postSelected(p1), "the post stays selected"); + ok(!tc.postSelected(p3), "it deselects the replies too"); + +}); + diff --git a/test/javascripts/helpers/qunit_helpers.js b/test/javascripts/helpers/qunit_helpers.js index 3ad3732e6..5254c24ae 100644 --- a/test/javascripts/helpers/qunit_helpers.js +++ b/test/javascripts/helpers/qunit_helpers.js @@ -14,6 +14,10 @@ function integration(name) { }); } +function testController(klass, model) { + return klass.create({model: model, container: Discourse.__container__}); +} + function controllerFor(controller, model) { var controller = Discourse.__container__.lookup('controller:' + controller); if (model) { controller.set('model', model ); } diff --git a/test/javascripts/jshint_all.js.erb b/test/javascripts/jshint_all.js.erb index 0515fa670..34b7eea68 100644 --- a/test/javascripts/jshint_all.js.erb +++ b/test/javascripts/jshint_all.js.erb @@ -122,6 +122,7 @@ var jsHintOpts = { "console", "alert", "controllerFor", + "testController", "containsInstance", "deepEqual", "notEqual",