From 8d6548b6109ec7a7d594a7011422baa92fa45684 Mon Sep 17 00:00:00 2001 From: flaburgan Date: Mon, 14 Jan 2019 00:31:38 +0100 Subject: [PATCH] Introduce like-interactions.js Adapt to latest development User likes Set css class for inline likes on comment Re-set participation on comment likes Co-authored-by: Thorsten Claus --- .../javascripts/app/collections/comments.js | 7 +- .../javascripts/app/collections/likes.js | 7 +- app/assets/javascripts/app/models/comment.js | 54 +---- .../app/models/post/interactions.js | 66 +----- .../app/models/post/like_interactions.js | 57 ++++++ .../javascripts/app/views/comment_view.js | 27 ++- .../app/views/stream_post_views.js | 2 +- app/assets/templates/comment_tpl.jst.hbs | 3 + .../templates/stream-element_tpl.jst.hbs | 2 +- app/controllers/api/v1/likes_controller.rb | 2 +- app/controllers/likes_controller.rb | 7 +- app/models/user/social_actions.rb | 4 + app/presenters/comment_presenter.rb | 19 +- app/services/comment_service.rb | 9 +- app/services/like_service.rb | 21 +- lib/schemas/api_v1.json | 11 +- spec/integration/api/likes_controller_spec.rb | 14 +- spec/javascripts/jasmine_helpers/factory.js | 12 +- spec/presenters/likes_presenter_spec.rb | 2 +- spec/services/like_service_spec.rb | 191 +++++++++++++++--- 20 files changed, 348 insertions(+), 169 deletions(-) create mode 100644 app/assets/javascripts/app/models/post/like_interactions.js diff --git a/app/assets/javascripts/app/collections/comments.js b/app/assets/javascripts/app/collections/comments.js index 9ecf4f047..55ffcd425 100644 --- a/app/assets/javascripts/app/collections/comments.js +++ b/app/assets/javascripts/app/collections/comments.js @@ -12,12 +12,17 @@ app.collections.Comments = Backbone.Collection.extend({ make : function(text) { var self = this; - var comment = new app.models.Comment({ "text": text }); + var comment = new app.models.Comment({"text": text}, {post: this.post}); var deferred = comment.save({}, { url: "/posts/"+ this.post.id +"/comments", success: function() { comment.set({author: app.currentUser.toJSON(), parent: self.post }); + + // Need interactions after make + comment.interactions = new app.models.Post.LikeInteractions( + _.extend({comment: comment, post: self.post}, comment.get("interactions")) + ); self.add(comment); } }); diff --git a/app/assets/javascripts/app/collections/likes.js b/app/assets/javascripts/app/collections/likes.js index 7eabdc6e4..7f42f9eaf 100644 --- a/app/assets/javascripts/app/collections/likes.js +++ b/app/assets/javascripts/app/collections/likes.js @@ -4,10 +4,11 @@ app.collections.Likes = Backbone.Collection.extend({ model: app.models.Like, initialize : function(models, options) { - this.url = (options.post != null) ? + // A comment- like has a post reference and a comment reference + this.url = (options.comment != null) ? // not delegating to post.url() because when it is in a stream collection it delegates to that url - "/posts/" + options.post.id + "/likes" : - "/comments/" + options.comment.id + "/likes"; + "/comments/" + options.comment.id + "/likes" : + "/posts/" + options.post.id + "/likes"; } }); // @license-end diff --git a/app/assets/javascripts/app/models/comment.js b/app/assets/javascripts/app/models/comment.js index da1e017b5..fb4c268f5 100644 --- a/app/assets/javascripts/app/models/comment.js +++ b/app/assets/javascripts/app/models/comment.js @@ -3,51 +3,15 @@ app.models.Comment = Backbone.Model.extend({ urlRoot: "/comments", - initialize: function() { - this.likes = new app.collections.Likes(this.get("likes"), {comment: this}); - }, - - // Copied from Post.Interaction. To be merged in an "interactable" class once comments can be commented too - likesCount: function() { - return this.get("likes_count"); - }, - - userLike: function() { - return this.likes.select(function(like) { - return like.get("author") && like.get("author").guid === app.currentUser.get("guid"); - })[0]; - }, - - toggleLike: function() { - if (this.userLike()) { - this.unlike(); - } else { - this.like(); - } - }, - - like: function() { - var self = this; - this.likes.create({}, { - success: function() { - self.post.set({participation: true}); - self.trigger("change"); - self.set({"likes_count": self.get("likes_count") + 1}); - self.likes.trigger("change"); - }, - error: function(model, response) { - app.flashMessages.handleAjaxError(response); - } - }); - }, - - unlike: function() { - var self = this; - this.userLike().destroy({success: function() { - self.trigger("change"); - self.set({"likes_count": self.get("likes_count") - 1}); - self.likes.trigger("change"); - }}); + initialize: function(model, options) { + options = options || {}; + this.post = model.post || options.post || this.collection.post; + this.interactions = new app.models.Post.LikeInteractions( + _.extend({comment: this, post: this.post}, this.get("interactions")) + ); + this.likes = this.interactions.likes; + this.likesCount = this.attributes.likes_count; + this.userLike = this.interactions.userLike(); } }); // @license-end diff --git a/app/assets/javascripts/app/models/post/interactions.js b/app/assets/javascripts/app/models/post/interactions.js index 572375c73..d07f075bc 100644 --- a/app/assets/javascripts/app/models/post/interactions.js +++ b/app/assets/javascripts/app/models/post/interactions.js @@ -1,75 +1,29 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later -//require ../post +//= require ./like_interactions -app.models.Post.Interactions = Backbone.Model.extend({ - initialize : function(options){ +app.models.Post.Interactions = app.models.Post.LikeInteractions.extend({ + initialize: function(options) { + app.models.Post.LikeInteractions.prototype.initialize.apply(this, arguments); this.post = options.post; - this.comments = new app.collections.Comments(this.get("comments"), {post : this.post}); - this.likes = new app.collections.Likes(this.get("likes"), {post : this.post}); - this.reshares = new app.collections.Reshares(this.get("reshares"), {post : this.post}); + this.comments = new app.collections.Comments(this.get("comments"), {post: this.post}); + this.reshares = new app.collections.Reshares(this.get("reshares"), {post: this.post}); }, - likesCount : function(){ - return this.get("likes_count"); - }, - - resharesCount : function(){ + resharesCount: function() { return this.get("reshares_count"); }, - commentsCount : function(){ + commentsCount: function() { return this.get("comments_count"); }, - userLike : function(){ - return this.likes.select(function(like){ - return like.get("author") && like.get("author").guid === app.currentUser.get("guid"); - })[0]; - }, - - userReshare : function(){ + userReshare: function() { return this.reshares.select(function(reshare){ return reshare.get("author") && reshare.get("author").guid === app.currentUser.get("guid"); })[0]; }, - toggleLike : function() { - if(this.userLike()) { - this.unlike(); - } else { - this.like(); - } - }, - - like : function() { - var self = this; - this.likes.create({}, { - success: function() { - self.post.set({participation: true}); - self.trigger("change"); - self.set({"likes_count" : self.get("likes_count") + 1}); - self.likes.trigger("change"); - }, - error: function(model, response) { - app.flashMessages.handleAjaxError(response); - } - }); - }, - - unlike : function() { - var self = this; - this.userLike().destroy({success : function() { - self.post.set({participation: false}); - self.trigger('change'); - self.set({"likes_count" : self.get("likes_count") - 1}); - self.likes.trigger("change"); - }, - error: function(model, response) { - app.flashMessages.handleAjaxError(response); - }}); - }, - comment: function(text, options) { var self = this; options = options || {}; @@ -109,7 +63,7 @@ app.models.Post.Interactions = Backbone.Model.extend({ }); }, - userCanReshare : function(){ + userCanReshare: function() { var isReshare = this.post.get("post_type") === "Reshare" , rootExists = (isReshare ? this.post.get("root") : true) , publicPost = this.post.get("public") diff --git a/app/assets/javascripts/app/models/post/like_interactions.js b/app/assets/javascripts/app/models/post/like_interactions.js new file mode 100644 index 000000000..29b102b67 --- /dev/null +++ b/app/assets/javascripts/app/models/post/like_interactions.js @@ -0,0 +1,57 @@ +// This class contains code extracted from interactions.js to factorize likes management between posts and comments + +app.models.Post.LikeInteractions = Backbone.Model.extend({ + + initialize: function(options) { + this.likes = new app.collections.Likes(this.get("likes"), options); + this.post = options.post; + }, + + likesCount: function() { + return this.get("likes_count"); + }, + + userLike: function() { + return this.likes.select(function(like) { + return like.get("author") && like.get("author").guid === app.currentUser.get("guid"); + })[0]; + }, + + toggleLike: function() { + if (this.userLike()) { + this.unlike(); + } else { + this.like(); + } + }, + + like: function() { + var self = this; + this.likes.create({}, { + success: function() { + self.post.set({participation: true}); + self.trigger("change"); + self.set({"likes_count": self.get("likes_count") + 1}); + self.likes.trigger("change"); + }, + error: function(model, response) { + app.flashMessages.handleAjaxError(response); + } + }); + }, + + unlike: function() { + var self = this; + this.userLike().destroy({ + success: function() { + // TODO: If user unlikes a post and the last like of all comments, then set participation to false + self.post.set({participation: false}); + self.trigger("change"); + self.set({"likes_count": self.get("likes_count") - 1}); + self.likes.trigger("change"); + }, + error: function(model, response) { + app.flashMessages.handleAjaxError(response); + }}); + } +}); diff --git a/app/assets/javascripts/app/views/comment_view.js b/app/assets/javascripts/app/views/comment_view.js index 1cd04ab62..f02258d60 100644 --- a/app/assets/javascripts/app/views/comment_view.js +++ b/app/assets/javascripts/app/views/comment_view.js @@ -6,7 +6,11 @@ app.views.Comment = app.views.Content.extend({ className : "comment media", tooltipSelector: "time", - events : function() { + subviews: { + ".likes-on-comment": "likesInfoView" + }, + + events: function() { return _.extend({}, app.views.Content.prototype.events, { "click .comment_delete": "destroyModel", "click .comment_report": "report", @@ -14,33 +18,40 @@ app.views.Comment = app.views.Content.extend({ }); }, - initialize : function(options){ + initialize: function(options) { this.templateName = options.templateName || this.templateName; + this.model.interactions.on("change", this.render, this); this.model.on("change", this.render, this); }, - presenter : function() { + presenter: function() { return _.extend(this.defaultPresenter(), { canRemove: this.canRemove(), - text: app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people")) + text: app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people")), + likesCount: this.model.attributes.likesCount, + userLike: this.model.interactions.userLike() }); }, - ownComment : function() { + ownComment: function() { return app.currentUser.authenticated() && this.model.get("author").diaspora_id === app.currentUser.get("diaspora_id"); }, - postOwner : function() { + postOwner: function() { return app.currentUser.authenticated() && this.model.get("parent").author.diaspora_id === app.currentUser.get("diaspora_id"); }, - canRemove : function() { + canRemove: function() { return app.currentUser.authenticated() && (this.ownComment() || this.postOwner()); }, toggleLike: function(evt) { if (evt) { evt.preventDefault(); } - this.model.toggleLike(); + this.model.interactions.toggleLike(); + }, + + likesInfoView: function() { + return new app.views.LikesInfo({model: this.model}); } }); diff --git a/app/assets/javascripts/app/views/stream_post_views.js b/app/assets/javascripts/app/views/stream_post_views.js index c454462ac..9843874d2 100644 --- a/app/assets/javascripts/app/views/stream_post_views.js +++ b/app/assets/javascripts/app/views/stream_post_views.js @@ -7,7 +7,7 @@ app.views.StreamPost = app.views.Post.extend({ subviews : { ".feedback": "feedbackView", ".comments": "commentStreamView", - ".likes": "likesInfoView", + ".likes-on-post": "likesInfoView", ".reshares": "resharesInfoView", ".post-controls": "postControlsView", ".post-content": "postContentView", diff --git a/app/assets/templates/comment_tpl.jst.hbs b/app/assets/templates/comment_tpl.jst.hbs index dd9480548..4d58f45e4 100644 --- a/app/assets/templates/comment_tpl.jst.hbs +++ b/app/assets/templates/comment_tpl.jst.hbs @@ -46,5 +46,8 @@ {{~/if~}} + + + diff --git a/app/assets/templates/stream-element_tpl.jst.hbs b/app/assets/templates/stream-element_tpl.jst.hbs index cb860b63a..27cd3aedb 100644 --- a/app/assets/templates/stream-element_tpl.jst.hbs +++ b/app/assets/templates/stream-element_tpl.jst.hbs @@ -37,7 +37,7 @@ {{#unless preview}} - +
{{/unless}} diff --git a/app/controllers/api/v1/likes_controller.rb b/app/controllers/api/v1/likes_controller.rb index d2d052f7f..4e0103bd3 100644 --- a/app/controllers/api/v1/likes_controller.rb +++ b/app/controllers/api/v1/likes_controller.rb @@ -33,7 +33,7 @@ module Api post = post_service.find!(params.require(:post_id)) raise ActiveRecord::RecordInvalid unless post.public? || private_read? - like_service.create(params[:post_id]) + like_service.create_for_post(params[:post_id]) rescue ActiveRecord::RecordInvalid => e if e.message == "Validation failed: Target has already been taken" return render_error 409, "Like already exists" diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb index ef79d0ad0..57182ac20 100644 --- a/app/controllers/likes_controller.rb +++ b/app/controllers/likes_controller.rb @@ -41,7 +41,12 @@ class LikesController < ApplicationController end def index - render json: like_service.find_for_post(params[:post_id]) + like = if params[:post_id] + like_service.find_for_post(params[:post_id]) + else + like_service.find_for_comment(params[:comment_id]) + end + render json: like .includes(author: :profile) .as_api_response(:backbone) end diff --git a/app/models/user/social_actions.rb b/app/models/user/social_actions.rb index 8a6369a18..3ab60f137 100644 --- a/app/models/user/social_actions.rb +++ b/app/models/user/social_actions.rb @@ -17,6 +17,10 @@ module User::SocialActions end end + def like_comment!(target, opts={}) + Like::Generator.new(self, target).create!(opts) + end + def participate_in_poll!(target, answer, opts={}) PollParticipation::Generator.new(self, target, answer).create!(opts).tap do update_or_create_participation!(target) diff --git a/app/presenters/comment_presenter.rb b/app/presenters/comment_presenter.rb index 6a290337e..7556c448f 100644 --- a/app/presenters/comment_presenter.rb +++ b/app/presenters/comment_presenter.rb @@ -8,7 +8,8 @@ class CommentPresenter < BasePresenter text: message.plain_text_for_json, author: author.as_api_response(:backbone), created_at: created_at, - mentioned_people: mentioned_people.as_api_response(:backbone) + mentioned_people: mentioned_people.as_api_response(:backbone), + interactions: build_interactions_json } end @@ -19,11 +20,25 @@ class CommentPresenter < BasePresenter author: PersonPresenter.new(author).as_api_json, created_at: created_at, mentioned_people: build_mentioned_people_json, - reported: current_user.present? && reports.where(user: current_user).exists? + reported: current_user.present? && reports.where(user: current_user).exists?, + interactions: build_interactions_json + } + end + + def build_interactions_json + { + likes: as_api(likes), + likes_count: likes_count } end def build_mentioned_people_json mentioned_people.map {|m| PersonPresenter.new(m).as_api_json } end + + def as_api(collection) + collection.includes(author: :profile).map {|element| + element.as_api_response(:backbone) + } + end end diff --git a/app/services/comment_service.rb b/app/services/comment_service.rb index 2e23033f8..adb370683 100644 --- a/app/services/comment_service.rb +++ b/app/services/comment_service.rb @@ -14,8 +14,8 @@ class CommentService post_service.find!(post_id).comments.for_a_stream end - def find!(comment_guid) - Comment.find_by!(guid: comment_guid) + def find!(id_or_guid) + Comment.find_by!(comment_key(id_or_guid) => id_or_guid) end def destroy(comment_id) @@ -45,6 +45,11 @@ class CommentService attr_reader :user + # We can assume a guid is at least 16 characters long as we have guids set to hex(8) since we started using them. + def comment_key(id_or_guid) + id_or_guid.to_s.length < 16 ? :id : :guid + end + def post_service @post_service ||= PostService.new(user) end diff --git a/app/services/like_service.rb b/app/services/like_service.rb index f3a17a6b3..2728a909a 100644 --- a/app/services/like_service.rb +++ b/app/services/like_service.rb @@ -12,7 +12,8 @@ class LikeService def create_for_comment(comment_id) comment = comment_service.find!(comment_id) - user.like!(comment) + post_service.find!(comment.commentable_id) # checks implicit for visible posts + user.like_comment!(comment) end def destroy(like_id) @@ -30,6 +31,13 @@ class LikeService user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes end + def find_for_comment(comment_id) + comment = comment_service.find!(comment_id) + post_service.find!(comment.post.id) # checks implicit for visible posts + likes = comment.likes + user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes + end + def unlike_post(post_id) likes = post_service.find!(post_id).likes likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC")) @@ -41,6 +49,17 @@ class LikeService end end + def unlike_comment(comment_id) + likes = comment_service.find!(comment_id).likes + likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC")) + if !likes.empty? && user.owns?(likes[0]) + user.retract(likes[0]) + true + else + false + end + end + private attr_reader :user diff --git a/lib/schemas/api_v1.json b/lib/schemas/api_v1.json index 2d807610a..d3650856b 100644 --- a/lib/schemas/api_v1.json +++ b/lib/schemas/api_v1.json @@ -101,9 +101,16 @@ "type": "array", "items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" } }, - "reported": { "type": "boolean" } + "reported": { "type": "boolean" }, + "interactions": { + "type": "object", + "properties" : { + "likes" : { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/likes"}, + "likes_count" : { "type": "integer"} + } + } }, - "required": ["guid", "created_at", "author", "body", "reported"], + "required": ["guid", "created_at", "author", "body", "reported", "interactions"], "additionalProperties": false } }, diff --git a/spec/integration/api/likes_controller_spec.rb b/spec/integration/api/likes_controller_spec.rb index 463919268..e70381494 100644 --- a/spec/integration/api/likes_controller_spec.rb +++ b/spec/integration/api/likes_controller_spec.rb @@ -61,9 +61,9 @@ describe Api::V1::LikesController do end it "succeeds in getting post with likes" do - like_service(bob).create(@status.guid) - like_service(auth.user).create(@status.guid) - like_service(alice).create(@status.guid) + like_service(bob).create_for_post(@status.guid) + like_service(auth.user).create_for_post(@status.guid) + like_service(alice).create_for_post(@status.guid) get( api_v1_post_likes_path(post_id: @status.guid), params: {access_token: access_token_minimum_scopes} @@ -112,7 +112,7 @@ describe Api::V1::LikesController do describe "#create" do context "with right post id" do - it "succeeeds in liking post" do + it "succeeds in liking post" do post( api_v1_post_likes_path(post_id: @status.guid), params: {access_token: access_token} @@ -181,7 +181,7 @@ describe Api::V1::LikesController do describe "#delete" do before do - like_service.create(@status.guid) + like_service.create_for_post(@status.guid) end context "with right post id" do @@ -225,7 +225,7 @@ describe Api::V1::LikesController do context "with improper credentials" do it "fails at unliking private post without private:read" do - like_service(auth_public_only.user).create(@private_status.guid) + like_service(auth_public_only.user).create_for_post(@private_status.guid) delete( api_v1_post_likes_path(post_id: @private_status.guid), params: {access_token: access_token} @@ -234,7 +234,7 @@ describe Api::V1::LikesController do end it "fails in unliking post without interactions" do - like_service(auth_minimum_scopes.user).create(@status.guid) + like_service(auth_minimum_scopes.user).create_for_post(@status.guid) delete( api_v1_post_likes_path(post_id: @status.guid), params: {access_token: access_token_minimum_scopes} diff --git a/spec/javascripts/jasmine_helpers/factory.js b/spec/javascripts/jasmine_helpers/factory.js index 9b0d4ecfc..4222c4c89 100644 --- a/spec/javascripts/jasmine_helpers/factory.js +++ b/spec/javascripts/jasmine_helpers/factory.js @@ -43,13 +43,15 @@ var factory = { comment : function(overrides) { var defaultAttrs = { - "created_at" : "2012-01-04T00:55:30Z", - "author" : this.author(), - "guid" : this.guid(), - "id" : this.id.next(), - "text" : "This is a comment!" + "created_at": "2012-01-04T00:55:30Z", + "author": this.author(), + "guid": this.guid(), + "id": this.id.next(), + "text": "This is a comment!" }; + overrides = overrides || {}; + overrides.post = this.post(); return new app.models.Comment(_.extend(defaultAttrs, overrides)); }, diff --git a/spec/presenters/likes_presenter_spec.rb b/spec/presenters/likes_presenter_spec.rb index 314c91d08..d3fd12635 100644 --- a/spec/presenters/likes_presenter_spec.rb +++ b/spec/presenters/likes_presenter_spec.rb @@ -10,7 +10,7 @@ describe LikesPresenter do to: "all" ) bobs_like_service = LikeService.new(bob) - like = bobs_like_service.create(@status.guid) + like = bobs_like_service.create_for_post(@status.guid) @presenter = LikesPresenter.new(like, bob) end diff --git a/spec/services/like_service_spec.rb b/spec/services/like_service_spec.rb index d52e495d6..a97cd8052 100644 --- a/spec/services/like_service_spec.rb +++ b/spec/services/like_service_spec.rb @@ -2,67 +2,129 @@ describe LikeService do let(:post) { alice.post(:status_message, text: "hello", to: alice.aspects.first) } + let(:alice_comment) { CommentService.new(alice).create(post.id, "This is a wonderful post") } + let(:bobs_comment) { CommentService.new(bob).create(post.id, "My post was better than yours") } - describe "#create" do + describe "#create_for_post" do it "creates a like on my own post" do expect { - LikeService.new(alice).create(post.id) + LikeService.new(alice).create_for_post(post.id) }.not_to raise_error end it "creates a like on a post of a contact" do expect { - LikeService.new(bob).create(post.id) + LikeService.new(bob).create_for_post(post.id) }.not_to raise_error end it "attaches the like to the post" do - like = LikeService.new(alice).create(post.id) + like = LikeService.new(alice).create_for_post(post.id) expect(post.likes.first.id).to eq(like.id) end it "fails if the post does not exist" do expect { - LikeService.new(bob).create("unknown id") + LikeService.new(bob).create_for_post("unknown id") }.to raise_error ActiveRecord::RecordNotFound end it "fails if the user can't see the post" do expect { - LikeService.new(eve).create(post.id) + LikeService.new(eve).create_for_post(post.id) }.to raise_error ActiveRecord::RecordNotFound end it "fails if the user already liked the post" do - LikeService.new(alice).create(post.id) + LikeService.new(alice).create_for_post(post.id) expect { - LikeService.new(alice).create(post.id) + LikeService.new(alice).create_for_post(post.id) + }.to raise_error ActiveRecord::RecordInvalid + end + end + + describe "#create_for_comment" do + it "creates a like on a posts comment" do + expect { + LikeService.new(alice).create_for_comment(alice_comment.id) + }.not_to raise_error + end + + it "creates a like on someone else comment" do + expect { + LikeService.new(alice).create_for_comment(bobs_comment.id) + }.not_to raise_error + end + + it "attaches the like to the comment" do + like = LikeService.new(alice).create_for_comment(bobs_comment.id) + expect(bobs_comment.likes.first.id).to eq(like.id) + end + + it "fails if comment does not exist" do + expect { + LikeService.new(alice).create_for_comment("unknown_id") + }.to raise_error ActiveRecord::RecordNotFound + end + + it "fails if user cant see post and its comments" do + expect { + LikeService.new(eve).create_for_comment(bobs_comment.id) + }.to raise_error ActiveRecord::RecordNotFound + end + + it "fails if user already liked the comment" do + LikeService.new(alice).create_for_comment(bobs_comment.id) + expect { + LikeService.new(alice).create_for_comment(bobs_comment.id) }.to raise_error ActiveRecord::RecordInvalid end end describe "#destroy" do - let(:like) { LikeService.new(bob).create(post.id) } + context "for post like" do + let(:like) { LikeService.new(bob).create_for_post(post.id) } - it "lets the user destroy their own like" do - result = LikeService.new(bob).destroy(like.id) - expect(result).to be_truthy + it "lets the user destroy their own like" do + result = LikeService.new(bob).destroy(like.id) + expect(result).to be_truthy + end + + it "doesn't let the parent author destroy others likes" do + result = LikeService.new(alice).destroy(like.id) + expect(result).to be_falsey + end + + it "doesn't let someone destroy others likes" do + result = LikeService.new(eve).destroy(like.id) + expect(result).to be_falsey + end + + it "fails if the like doesn't exist" do + expect { + LikeService.new(bob).destroy("unknown id") + }.to raise_error ActiveRecord::RecordNotFound + end end - it "doesn't let the parent author destroy others likes" do - result = LikeService.new(alice).destroy(like.id) - expect(result).to be_falsey - end + context "for comment like" do + let(:like) { LikeService.new(bob).create_for_comment(alice_comment.id) } - it "doesn't let someone destroy others likes" do - result = LikeService.new(eve).destroy(like.id) - expect(result).to be_falsey - end + it "let the user destroy its own comment like" do + result = LikeService.new(bob).destroy(like.id) + expect(result).to be_truthy + end - it "fails if the like doesn't exist" do - expect { - LikeService.new(bob).destroy("unknown id") - }.to raise_error ActiveRecord::RecordNotFound + it "doesn't let the parent author destroy other comment likes" do + result = LikeService.new(alice).destroy(like.id) + expect(result).to be_falsey + end + + it "fails if the like doesn't exist" do + expect { + LikeService.new(alice).destroy("unknown id") + }.to raise_error ActiveRecord::RecordNotFound + end end end @@ -70,17 +132,17 @@ describe LikeService do context "with user" do it "returns likes for a public post" do post = alice.post(:status_message, text: "hello", public: true) - like = LikeService.new(alice).create(post.id) + like = LikeService.new(alice).create_for_post(post.id) expect(LikeService.new(eve).find_for_post(post.id)).to include(like) end it "returns likes for a visible private post" do - like = LikeService.new(alice).create(post.id) + like = LikeService.new(alice).create_for_post(post.id) expect(LikeService.new(bob).find_for_post(post.id)).to include(like) end it "doesn't return likes for a private post the user can not see" do - LikeService.new(alice).create(post.id) + LikeService.new(alice).create_for_post(post.id) expect { LikeService.new(eve).find_for_post(post.id) }.to raise_error ActiveRecord::RecordNotFound @@ -88,7 +150,7 @@ describe LikeService do it "returns the user's like first" do post = alice.post(:status_message, text: "hello", public: true) - [alice, bob, eve].map {|user| LikeService.new(user).create(post.id) } + [alice, bob, eve].map {|user| LikeService.new(user).create_for_post(post.id) } [alice, bob, eve].each do |user| expect( @@ -101,12 +163,12 @@ describe LikeService do context "without user" do it "returns likes for a public post" do post = alice.post(:status_message, text: "hello", public: true) - like = LikeService.new(alice).create(post.id) + like = LikeService.new(alice).create_for_post(post.id) expect(LikeService.new.find_for_post(post.id)).to include(like) end it "doesn't return likes a for private post" do - LikeService.new(alice).create(post.id) + LikeService.new(alice).create_for_post(post.id) expect { LikeService.new.find_for_post(post.id) }.to raise_error Diaspora::NonPublic @@ -115,15 +177,68 @@ describe LikeService do it "returns all likes of a post" do post = alice.post(:status_message, text: "hello", public: true) - likes = [alice, bob, eve].map {|user| LikeService.new(user).create(post.id) } + likes = [alice, bob, eve].map {|user| LikeService.new(user).create_for_post(post.id) } expect(LikeService.new.find_for_post(post.id)).to match_array(likes) end end + describe "#find_for_comment" do + context "with user" do + it "returns likes for a public post comment" do + post = alice.post(:status_message, text: "hello", public: true) + comment = CommentService.new(bob).create(post.id, "Hello comment") + like = LikeService.new(alice).create_for_comment(comment.id) + expect(LikeService.new(eve).find_for_comment(comment.id)).to include(like) + end + + it "returns likes for visible private post comments" do + comment = CommentService.new(bob).create(post.id, "Hello comment") + like = LikeService.new(alice).create_for_comment(comment.id) + expect(LikeService.new(bob).find_for_comment(comment.id)).to include(like) + end + + it "doesn't return likes for a posts comment the user can not see" do + expect { + LikeService.new(eve).find_for_comment(alice_comment.id) + }.to raise_error ActiveRecord::RecordNotFound + end + + it "returns the user's like first" do + post = alice.post(:status_message, text: "hello", public: true) + comment = CommentService.new(alice).create(post.id, "I like my own post") + + [alice, bob, eve].map {|user| LikeService.new(user).create_for_comment(comment.id) } + [alice, bob, eve].each do |user| + expect( + LikeService.new(user).find_for_comment(comment.id).first.author.id + ).to be user.person.id + end + end + end + + context "without user" do + it "returns likes for a comment on a public post" do + post = alice.post(:status_message, text: "hello", public: true) + comment = CommentService.new(bob).create(post.id, "I like my own post") + like = LikeService.new(alice).create_for_comment(comment.id) + expect( + LikeService.new.find_for_comment(comment.id) + ).to include(like) + end + + it "doesn't return likes for a private post comment" do + LikeService.new(alice).create_for_comment(alice_comment.id) + expect { + LikeService.new.find_for_comment(alice_comment.id) + }.to raise_error Diaspora::NonPublic + end + end + end + describe "#unlike_post" do before do - LikeService.new(alice).create(post.id) + LikeService.new(alice).create_for_post(post.id) end it "removes the like to the post" do @@ -131,4 +246,16 @@ describe LikeService do expect(post.likes.length).to eq(0) end end + + describe "#unlike_comment" do + it "removes the like for a comment" do + comment = CommentService.new(alice).create(post.id, "I like my own post") + LikeService.new(alice).create_for_comment(comment.id) + expect(comment.likes.length).to eq(1) + + LikeService.new(alice).unlike_comment(comment.id) + comment = CommentService.new(alice).find!(comment.id) + expect(comment.likes.length).to eq(0) + end + end end