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 <ThorstenClaus@web.de>
This commit is contained in:
flaburgan 2019-01-14 00:31:38 +01:00 committed by Benjamin Neff
parent 82ff57a750
commit 8d6548b610
No known key found for this signature in database
GPG key ID: 971464C3F1A90194
20 changed files with 348 additions and 169 deletions

View file

@ -12,12 +12,17 @@ app.collections.Comments = Backbone.Collection.extend({
make : function(text) { make : function(text) {
var self = this; 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({}, { var deferred = comment.save({}, {
url: "/posts/"+ this.post.id +"/comments", url: "/posts/"+ this.post.id +"/comments",
success: function() { success: function() {
comment.set({author: app.currentUser.toJSON(), parent: self.post }); 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); self.add(comment);
} }
}); });

View file

@ -4,10 +4,11 @@ app.collections.Likes = Backbone.Collection.extend({
model: app.models.Like, model: app.models.Like,
initialize : function(models, options) { 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 // 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 // @license-end

View file

@ -3,51 +3,15 @@
app.models.Comment = Backbone.Model.extend({ app.models.Comment = Backbone.Model.extend({
urlRoot: "/comments", urlRoot: "/comments",
initialize: function() { initialize: function(model, options) {
this.likes = new app.collections.Likes(this.get("likes"), {comment: this}); options = options || {};
}, this.post = model.post || options.post || this.collection.post;
this.interactions = new app.models.Post.LikeInteractions(
// Copied from Post.Interaction. To be merged in an "interactable" class once comments can be commented too _.extend({comment: this, post: this.post}, this.get("interactions"))
likesCount: function() { );
return this.get("likes_count"); this.likes = this.interactions.likes;
}, this.likesCount = this.attributes.likes_count;
this.userLike = this.interactions.userLike();
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");
}});
} }
}); });
// @license-end // @license-end

View file

@ -1,75 +1,29 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later // @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({ app.models.Post.Interactions = app.models.Post.LikeInteractions.extend({
initialize : function(options){ initialize: function(options) {
app.models.Post.LikeInteractions.prototype.initialize.apply(this, arguments);
this.post = options.post; this.post = options.post;
this.comments = new app.collections.Comments(this.get("comments"), {post : this.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.reshares = new app.collections.Reshares(this.get("reshares"), {post : this.post});
}, },
likesCount : function(){ resharesCount: function() {
return this.get("likes_count");
},
resharesCount : function(){
return this.get("reshares_count"); return this.get("reshares_count");
}, },
commentsCount : function(){ commentsCount: function() {
return this.get("comments_count"); return this.get("comments_count");
}, },
userLike : function(){ userReshare: function() {
return this.likes.select(function(like){
return like.get("author") && like.get("author").guid === app.currentUser.get("guid");
})[0];
},
userReshare : function(){
return this.reshares.select(function(reshare){ return this.reshares.select(function(reshare){
return reshare.get("author") && reshare.get("author").guid === app.currentUser.get("guid"); return reshare.get("author") && reshare.get("author").guid === app.currentUser.get("guid");
})[0]; })[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) { comment: function(text, options) {
var self = this; var self = this;
options = options || {}; 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" var isReshare = this.post.get("post_type") === "Reshare"
, rootExists = (isReshare ? this.post.get("root") : true) , rootExists = (isReshare ? this.post.get("root") : true)
, publicPost = this.post.get("public") , publicPost = this.post.get("public")

View file

@ -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);
}});
}
});

View file

@ -6,7 +6,11 @@ app.views.Comment = app.views.Content.extend({
className : "comment media", className : "comment media",
tooltipSelector: "time", tooltipSelector: "time",
events : function() { subviews: {
".likes-on-comment": "likesInfoView"
},
events: function() {
return _.extend({}, app.views.Content.prototype.events, { return _.extend({}, app.views.Content.prototype.events, {
"click .comment_delete": "destroyModel", "click .comment_delete": "destroyModel",
"click .comment_report": "report", "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.templateName = options.templateName || this.templateName;
this.model.interactions.on("change", this.render, this);
this.model.on("change", this.render, this); this.model.on("change", this.render, this);
}, },
presenter : function() { presenter: function() {
return _.extend(this.defaultPresenter(), { return _.extend(this.defaultPresenter(), {
canRemove: this.canRemove(), 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"); 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"); 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()); return app.currentUser.authenticated() && (this.ownComment() || this.postOwner());
}, },
toggleLike: function(evt) { toggleLike: function(evt) {
if (evt) { evt.preventDefault(); } if (evt) { evt.preventDefault(); }
this.model.toggleLike(); this.model.interactions.toggleLike();
},
likesInfoView: function() {
return new app.views.LikesInfo({model: this.model});
} }
}); });

View file

@ -7,7 +7,7 @@ app.views.StreamPost = app.views.Post.extend({
subviews : { subviews : {
".feedback": "feedbackView", ".feedback": "feedbackView",
".comments": "commentStreamView", ".comments": "commentStreamView",
".likes": "likesInfoView", ".likes-on-post": "likesInfoView",
".reshares": "resharesInfoView", ".reshares": "resharesInfoView",
".post-controls": "postControlsView", ".post-controls": "postControlsView",
".post-content": "postContentView", ".post-content": "postContentView",

View file

@ -46,5 +46,8 @@
{{~/if~}} {{~/if~}}
</a> </a>
</div> </div>
<div class="likes likes-on-comment"> </div>
</div> </div>
</div> </div>

View file

@ -37,7 +37,7 @@
{{#unless preview}} {{#unless preview}}
<div class="feedback nsfw-hidden"> </div> <div class="feedback nsfw-hidden"> </div>
<div class="likes nsfw-hidden"> </div> <div class="likes likes-on-post nsfw-hidden"> </div>
<div class="reshares nsfw-hidden"> </div> <div class="reshares nsfw-hidden"> </div>
<div class="comments nsfw-hidden"> </div> <div class="comments nsfw-hidden"> </div>
{{/unless}} {{/unless}}

View file

@ -33,7 +33,7 @@ module Api
post = post_service.find!(params.require(:post_id)) post = post_service.find!(params.require(:post_id))
raise ActiveRecord::RecordInvalid unless post.public? || private_read? 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 rescue ActiveRecord::RecordInvalid => e
if e.message == "Validation failed: Target has already been taken" if e.message == "Validation failed: Target has already been taken"
return render_error 409, "Like already exists" return render_error 409, "Like already exists"

View file

@ -41,7 +41,12 @@ class LikesController < ApplicationController
end end
def index 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) .includes(author: :profile)
.as_api_response(:backbone) .as_api_response(:backbone)
end end

View file

@ -17,6 +17,10 @@ module User::SocialActions
end end
end end
def like_comment!(target, opts={})
Like::Generator.new(self, target).create!(opts)
end
def participate_in_poll!(target, answer, opts={}) def participate_in_poll!(target, answer, opts={})
PollParticipation::Generator.new(self, target, answer).create!(opts).tap do PollParticipation::Generator.new(self, target, answer).create!(opts).tap do
update_or_create_participation!(target) update_or_create_participation!(target)

View file

@ -8,7 +8,8 @@ class CommentPresenter < BasePresenter
text: message.plain_text_for_json, text: message.plain_text_for_json,
author: author.as_api_response(:backbone), author: author.as_api_response(:backbone),
created_at: created_at, 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 end
@ -19,11 +20,25 @@ class CommentPresenter < BasePresenter
author: PersonPresenter.new(author).as_api_json, author: PersonPresenter.new(author).as_api_json,
created_at: created_at, created_at: created_at,
mentioned_people: build_mentioned_people_json, 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 end
def build_mentioned_people_json def build_mentioned_people_json
mentioned_people.map {|m| PersonPresenter.new(m).as_api_json } mentioned_people.map {|m| PersonPresenter.new(m).as_api_json }
end end
def as_api(collection)
collection.includes(author: :profile).map {|element|
element.as_api_response(:backbone)
}
end
end end

View file

@ -14,8 +14,8 @@ class CommentService
post_service.find!(post_id).comments.for_a_stream post_service.find!(post_id).comments.for_a_stream
end end
def find!(comment_guid) def find!(id_or_guid)
Comment.find_by!(guid: comment_guid) Comment.find_by!(comment_key(id_or_guid) => id_or_guid)
end end
def destroy(comment_id) def destroy(comment_id)
@ -45,6 +45,11 @@ class CommentService
attr_reader :user 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 def post_service
@post_service ||= PostService.new(user) @post_service ||= PostService.new(user)
end end

View file

@ -12,7 +12,8 @@ class LikeService
def create_for_comment(comment_id) def create_for_comment(comment_id)
comment = comment_service.find!(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 end
def destroy(like_id) def destroy(like_id)
@ -30,6 +31,13 @@ class LikeService
user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes
end 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) def unlike_post(post_id)
likes = post_service.find!(post_id).likes likes = post_service.find!(post_id).likes
likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC")) likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC"))
@ -41,6 +49,17 @@ class LikeService
end end
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 private
attr_reader :user attr_reader :user

View file

@ -101,9 +101,16 @@
"type": "array", "type": "array",
"items": { "$ref": "https://diaspora.software/api/v1/schema.json#/definitions/short_profile" } "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 "additionalProperties": false
} }
}, },

View file

@ -61,9 +61,9 @@ describe Api::V1::LikesController do
end end
it "succeeds in getting post with likes" do it "succeeds in getting post with likes" do
like_service(bob).create(@status.guid) like_service(bob).create_for_post(@status.guid)
like_service(auth.user).create(@status.guid) like_service(auth.user).create_for_post(@status.guid)
like_service(alice).create(@status.guid) like_service(alice).create_for_post(@status.guid)
get( get(
api_v1_post_likes_path(post_id: @status.guid), api_v1_post_likes_path(post_id: @status.guid),
params: {access_token: access_token_minimum_scopes} params: {access_token: access_token_minimum_scopes}
@ -112,7 +112,7 @@ describe Api::V1::LikesController do
describe "#create" do describe "#create" do
context "with right post id" do context "with right post id" do
it "succeeeds in liking post" do it "succeeds in liking post" do
post( post(
api_v1_post_likes_path(post_id: @status.guid), api_v1_post_likes_path(post_id: @status.guid),
params: {access_token: access_token} params: {access_token: access_token}
@ -181,7 +181,7 @@ describe Api::V1::LikesController do
describe "#delete" do describe "#delete" do
before do before do
like_service.create(@status.guid) like_service.create_for_post(@status.guid)
end end
context "with right post id" do context "with right post id" do
@ -225,7 +225,7 @@ describe Api::V1::LikesController do
context "with improper credentials" do context "with improper credentials" do
it "fails at unliking private post without private:read" 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( delete(
api_v1_post_likes_path(post_id: @private_status.guid), api_v1_post_likes_path(post_id: @private_status.guid),
params: {access_token: access_token} params: {access_token: access_token}
@ -234,7 +234,7 @@ describe Api::V1::LikesController do
end end
it "fails in unliking post without interactions" do 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( delete(
api_v1_post_likes_path(post_id: @status.guid), api_v1_post_likes_path(post_id: @status.guid),
params: {access_token: access_token_minimum_scopes} params: {access_token: access_token_minimum_scopes}

View file

@ -43,13 +43,15 @@ var factory = {
comment : function(overrides) { comment : function(overrides) {
var defaultAttrs = { var defaultAttrs = {
"created_at" : "2012-01-04T00:55:30Z", "created_at": "2012-01-04T00:55:30Z",
"author" : this.author(), "author": this.author(),
"guid" : this.guid(), "guid": this.guid(),
"id" : this.id.next(), "id": this.id.next(),
"text" : "This is a comment!" "text": "This is a comment!"
}; };
overrides = overrides || {};
overrides.post = this.post();
return new app.models.Comment(_.extend(defaultAttrs, overrides)); return new app.models.Comment(_.extend(defaultAttrs, overrides));
}, },

View file

@ -10,7 +10,7 @@ describe LikesPresenter do
to: "all" to: "all"
) )
bobs_like_service = LikeService.new(bob) 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) @presenter = LikesPresenter.new(like, bob)
end end

View file

@ -2,67 +2,129 @@
describe LikeService do describe LikeService do
let(:post) { alice.post(:status_message, text: "hello", to: alice.aspects.first) } 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 it "creates a like on my own post" do
expect { expect {
LikeService.new(alice).create(post.id) LikeService.new(alice).create_for_post(post.id)
}.not_to raise_error }.not_to raise_error
end end
it "creates a like on a post of a contact" do it "creates a like on a post of a contact" do
expect { expect {
LikeService.new(bob).create(post.id) LikeService.new(bob).create_for_post(post.id)
}.not_to raise_error }.not_to raise_error
end end
it "attaches the like to the post" do 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) expect(post.likes.first.id).to eq(like.id)
end end
it "fails if the post does not exist" do it "fails if the post does not exist" do
expect { expect {
LikeService.new(bob).create("unknown id") LikeService.new(bob).create_for_post("unknown id")
}.to raise_error ActiveRecord::RecordNotFound }.to raise_error ActiveRecord::RecordNotFound
end end
it "fails if the user can't see the post" do it "fails if the user can't see the post" do
expect { expect {
LikeService.new(eve).create(post.id) LikeService.new(eve).create_for_post(post.id)
}.to raise_error ActiveRecord::RecordNotFound }.to raise_error ActiveRecord::RecordNotFound
end end
it "fails if the user already liked the post" do 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 { 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 }.to raise_error ActiveRecord::RecordInvalid
end end
end end
describe "#destroy" do 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 it "lets the user destroy their own like" do
result = LikeService.new(bob).destroy(like.id) result = LikeService.new(bob).destroy(like.id)
expect(result).to be_truthy 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 end
it "doesn't let the parent author destroy others likes" do context "for comment like" do
result = LikeService.new(alice).destroy(like.id) let(:like) { LikeService.new(bob).create_for_comment(alice_comment.id) }
expect(result).to be_falsey
end
it "doesn't let someone destroy others likes" do it "let the user destroy its own comment like" do
result = LikeService.new(eve).destroy(like.id) result = LikeService.new(bob).destroy(like.id)
expect(result).to be_falsey expect(result).to be_truthy
end end
it "fails if the like doesn't exist" do it "doesn't let the parent author destroy other comment likes" do
expect { result = LikeService.new(alice).destroy(like.id)
LikeService.new(bob).destroy("unknown id") expect(result).to be_falsey
}.to raise_error ActiveRecord::RecordNotFound 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
end end
@ -70,17 +132,17 @@ describe LikeService do
context "with user" do context "with user" do
it "returns likes for a public post" do it "returns likes for a public post" do
post = alice.post(:status_message, text: "hello", public: true) 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) expect(LikeService.new(eve).find_for_post(post.id)).to include(like)
end end
it "returns likes for a visible private post" do 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) expect(LikeService.new(bob).find_for_post(post.id)).to include(like)
end end
it "doesn't return likes for a private post the user can not see" do 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 { expect {
LikeService.new(eve).find_for_post(post.id) LikeService.new(eve).find_for_post(post.id)
}.to raise_error ActiveRecord::RecordNotFound }.to raise_error ActiveRecord::RecordNotFound
@ -88,7 +150,7 @@ describe LikeService do
it "returns the user's like first" do it "returns the user's like first" do
post = alice.post(:status_message, text: "hello", public: true) 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| [alice, bob, eve].each do |user|
expect( expect(
@ -101,12 +163,12 @@ describe LikeService do
context "without user" do context "without user" do
it "returns likes for a public post" do it "returns likes for a public post" do
post = alice.post(:status_message, text: "hello", public: true) 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) expect(LikeService.new.find_for_post(post.id)).to include(like)
end end
it "doesn't return likes a for private post" do 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 { expect {
LikeService.new.find_for_post(post.id) LikeService.new.find_for_post(post.id)
}.to raise_error Diaspora::NonPublic }.to raise_error Diaspora::NonPublic
@ -115,15 +177,68 @@ describe LikeService do
it "returns all likes of a post" do it "returns all likes of a post" do
post = alice.post(:status_message, text: "hello", public: true) 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) expect(LikeService.new.find_for_post(post.id)).to match_array(likes)
end end
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 describe "#unlike_post" do
before do before do
LikeService.new(alice).create(post.id) LikeService.new(alice).create_for_post(post.id)
end end
it "removes the like to the post" do it "removes the like to the post" do
@ -131,4 +246,16 @@ describe LikeService do
expect(post.likes.length).to eq(0) expect(post.likes.length).to eq(0)
end end
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 end