diff --git a/app/assets/javascripts/app/collections/comments.js b/app/assets/javascripts/app/collections/comments.js index efd825988d8fde9bcf00d0db7bd0baf8f721a31b..0b53b8cf0db9ae46323a760cbaa72489df0ec0f0 100644 --- a/app/assets/javascripts/app/collections/comments.js +++ b/app/assets/javascripts/app/collections/comments.js @@ -7,5 +7,18 @@ app.collections.Comments = Backbone.Collection.extend({ initialize : function(models, options) { this.post = options.post + }, + + make : function(text){ + var self = this + + var comment = new app.models.Comment({text: text }) + , deferred = comment.save({}, {url : self.url()}) + + comment.set({author: app.currentUser.toJSON(), parent: self.post }) + + this.add(comment) + + return deferred } }); diff --git a/app/assets/javascripts/app/collections/reshares.js b/app/assets/javascripts/app/collections/reshares.js new file mode 100644 index 0000000000000000000000000000000000000000..d2c74c8e530a769cff06616cd05e5c5bda0dba75 --- /dev/null +++ b/app/assets/javascripts/app/collections/reshares.js @@ -0,0 +1,4 @@ +app.collections.Reshares = Backbone.Collection.extend({ + model: app.models.Reshare, + url : "/reshares" +}); diff --git a/app/assets/javascripts/app/models/participation.js b/app/assets/javascripts/app/models/participation.js deleted file mode 100644 index 01c53d9e2be3735a1359a06380752a20321a5ce4..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/app/models/participation.js +++ /dev/null @@ -1 +0,0 @@ -app.models.Participation = Backbone.Model.extend({ }); diff --git a/app/assets/javascripts/app/models/post.js b/app/assets/javascripts/app/models/post.js index e6f01de60d83ed7183ba68e5f16ea7036f065c81..7ed5c69cdea4a4cf669539d2cadc67aff8bb2fa8 100644 --- a/app/assets/javascripts/app/models/post.js +++ b/app/assets/javascripts/app/models/post.js @@ -2,27 +2,27 @@ app.models.Post = Backbone.Model.extend(_.extend({}, app.models.formatDateMixin, urlRoot : "/posts", initialize : function() { - this.setupCollections(); - this.bind("change", this.setupCollections, this) + this.interactions = new app.models.Post.Interactions(_.extend({post : this}, this.get("interactions"))) + this.delegateToInteractions() }, - setupCollections: function() { - this.comments = new app.collections.Comments(this.get("comments") || this.get("last_three_comments"), {post : this}); - this.likes = this.likes || new app.collections.Likes([], {post : this}); // load in the user like initially - this.participations = this.participations || new app.collections.Participations([], {post : this}); // load in the user like initially + delegateToInteractions : function(){ + this.comments = this.interactions.comments + this.likes = this.interactions.likes + + this.comment = function(){ + this.interactions.comment.apply(this.interactions, arguments) + } }, setFrameName : function(){ - var templatePicker = new app.models.Post.TemplatePicker(this) - this.set({frame_name : templatePicker.getFrameName()}) + this.set({frame_name : new app.models.Post.TemplatePicker(this).getFrameName()}) }, interactedAt : function() { return this.timeOf("interacted_at"); }, - createReshareUrl : "/reshares", - reshare : function(){ return this._reshare = this._reshare || new app.models.Reshare({root_guid : this.get("guid")}); }, @@ -31,15 +31,6 @@ app.models.Post = Backbone.Model.extend(_.extend({}, app.models.formatDateMixin, return this.get("author") }, - toggleLike : function() { - var userLike = this.get("user_like") - if(userLike) { - this.unlike() - } else { - this.like() - } - }, - toggleFavorite : function(options){ this.set({favorite : !this.get("favorite")}) @@ -47,40 +38,6 @@ app.models.Post = Backbone.Model.extend(_.extend({}, app.models.formatDateMixin, if(options.save){ this.save() } }, - like : function() { - var self = this; - this.likes.create({}, {success : function(resp){ - self.set(resp) - self.trigger('interacted', self) - }}); - - }, - - unlike : function() { - var self = this; - var likeModel = new app.models.Like(this.get("user_like")); - likeModel.url = this.likes.url + "/" + likeModel.id; - - likeModel.destroy({success : function(model, resp) { - self.set(resp); - self.trigger('interacted', this) - }}); - }, - - comment : function (text) { - - var self = this - , postComments = this.comments; - - postComments.create({"text": text}, { - url : postComments.url(), - wait:true, // added a wait for the time being. 0.5.3 was not optimistic, but 0.9.2 is. - error:function () { - alert(Diaspora.I18n.t("failed_to_post_message")); - } - }); - }, - headline : function() { var headline = this.get("text").trim() , newlineIdx = headline.indexOf("\n") diff --git a/app/assets/javascripts/app/models/post/interactions.js b/app/assets/javascripts/app/models/post/interactions.js new file mode 100644 index 0000000000000000000000000000000000000000..0cd51de819b09894278ae95eb75bfb76dc587e64 --- /dev/null +++ b/app/assets/javascripts/app/models/post/interactions.js @@ -0,0 +1,116 @@ +//require ../post + +app.models.Post.Interactions = Backbone.Model.extend({ + url : function(){ + return this.post.url() + "/interactions" + }, + + initialize : function(options){ + 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}); + }, + + parse : function(resp){ + this.comments.reset(resp.comments) + this.likes.reset(resp.likes) + this.reshares.reset(resp.reshares) + + var comments = this.comments + , likes = this.likes + , reshares = this.reshares + + return { + comments : comments, + likes : likes, + reshares : reshares, + fetched : true + } + }, + + likesCount : function(){ + return (this.get("fetched") ? this.likes.models.length : this.get("likes_count") ) + }, + + resharesCount : function(){ + return this.get("fetched") ? this.reshares.models.length : this.get("reshares_count") + }, + + commentsCount : function(){ + return this.get("fetched") ? this.comments.models.length : this.get("comments_count") + }, + + userLike : function(){ + return this.likes.select(function(like){ return like.get("author").guid == app.currentUser.get("guid")})[0] + }, + + userReshare : function(){ + return this.reshares.select(function(reshare){ return 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.trigger("change") + self.set({"likes_count" : self.get("likes_count") + 1}) + }}) + }, + + unlike : function() { + var self = this; + this.userLike().destroy({success : function(model, resp) { + self.trigger('change') + self.set({"likes_count" : self.get("likes_count") - 1}) + }}); + }, + + comment : function (text) { + var self = this; + + this.comments.make(text).fail(function () { + alert(Diaspora.I18n.t("failed_to_post_message")); + }).done(function() { + self.trigger('change') //updates after sync + }); + + this.trigger("change") //updates count in an eager manner + }, + + reshare : function(){ + var interactions = this + , reshare = this.post.reshare() + + reshare.save({}, { + success : function(resp){ + var flash = new Diaspora.Widgets.FlashMessages; + flash.render({ + success: true, + notice: Diaspora.I18n.t("reshares.successful") + }); + } + }).done(function(){ + interactions.reshares.add(reshare) + }).done(function(){ + interactions.trigger("change") + }); + }, + + userCanReshare : function(){ + var isReshare = this.post.get("post_type") == "Reshare" + , rootExists = (isReshare ? this.post.get("root") : true) + , publicPost = this.post.get("public") + , userIsNotAuthor = this.post.get("author").diaspora_id != app.currentUser.get("diaspora_id") + , userIsNotRootAuthor = rootExists && (isReshare ? this.post.get("root").author.diaspora_id != app.currentUser.get("diaspora_id") : true) + + return publicPost && app.currentUser.authenticated() && userIsNotAuthor && userIsNotRootAuthor; + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/app/models/reshare.js b/app/assets/javascripts/app/models/reshare.js index 68320eb80e81d228c4aaec76809170b84487bee2..7a4b83ff7d99a7da0daea371fc4cb4335fcf5dd1 100644 --- a/app/assets/javascripts/app/models/reshare.js +++ b/app/assets/javascripts/app/models/reshare.js @@ -1,4 +1,6 @@ app.models.Reshare = app.models.Post.extend({ + urlRoot : "/reshares", + rootPost : function(){ this._rootPost = this._rootPost || new app.models.Post(this.get("root")); return this._rootPost diff --git a/app/assets/javascripts/app/pages/post-viewer.js b/app/assets/javascripts/app/pages/post-viewer.js index 17b0c19f895e3870137bb6fa586703cfc9fa3572..71f1d663d24afac15ff692a1abd88c0e8f9774a1 100644 --- a/app/assets/javascripts/app/pages/post-viewer.js +++ b/app/assets/javascripts/app/pages/post-viewer.js @@ -9,8 +9,10 @@ app.pages.PostViewer = app.views.Base.extend({ }, initialize : function(options) { - this.model = new app.models.Post({ id : options.id }); + var post = this.model = new app.models.Post({ id : options.id }); this.model.preloadOrFetch().done(_.bind(this.initViews, this)); + this.model.interactions.fetch() //async, yo, might want to throttle this later. + this.bindEvents() }, diff --git a/app/assets/javascripts/app/views/comment_stream_view.js b/app/assets/javascripts/app/views/comment_stream_view.js index c97374fefcc18125882e2217e3f1cdd69a765e4c..fbdda24d5a29fe542668222c34c694d9c8c904e9 100644 --- a/app/assets/javascripts/app/views/comment_stream_view.js +++ b/app/assets/javascripts/app/views/comment_stream_view.js @@ -31,8 +31,9 @@ app.views.CommentStream = app.views.Base.extend({ presenter: function(){ return _.extend(this.defaultPresenter(), { - moreCommentsCount : (this.model.get("comments_count") - 3), - showExpandCommentsLink : (this.model.get("comments_count") > 3) + moreCommentsCount : (this.model.interactions.commentsCount() - 3), + showExpandCommentsLink : (this.model.interactions.commentsCount() > 3), + commentsCount : this.model.interactions.commentsCount() }) }, diff --git a/app/assets/javascripts/app/views/comment_view.js b/app/assets/javascripts/app/views/comment_view.js index cbed276a5220c1fdf7e2df6660c2f2dea5859118..4a826a33df8dbc7d7e5322b849c3755d02842dea 100644 --- a/app/assets/javascripts/app/views/comment_view.js +++ b/app/assets/javascripts/app/views/comment_view.js @@ -4,11 +4,15 @@ app.views.Comment = app.views.Content.extend({ className : "comment media", events : function() { - return _.extend(app.views.Content.prototype.events, { + return _.extend({}, app.views.Content.prototype.events, { "click .comment_delete": "destroyModel" }); }, + initialize : function(){ + this.model.on("change", this.render, this) + }, + presenter : function() { return _.extend(this.defaultPresenter(), { canRemove: this.canRemove(), diff --git a/app/assets/javascripts/app/views/feedback_view.js b/app/assets/javascripts/app/views/feedback_view.js index 86f83bb7f95abf3fb804de6b4517010db41f966f..57e3a8ac8402e0824f4f69ba3084d8ef5b530bef 100644 --- a/app/assets/javascripts/app/views/feedback_view.js +++ b/app/assets/javascripts/app/views/feedback_view.js @@ -1,5 +1,4 @@ app.views.Feedback = app.views.Base.extend({ - templateName: "feedback", className : "info", @@ -10,47 +9,30 @@ app.views.Feedback = app.views.Base.extend({ }, initialize : function() { - this.model.bind('interacted', this.render, this); + this.model.interactions.on('change', this.render, this); }, presenter : function() { - return _.extend(this.defaultPresenter(), { - userCanReshare : this.userCanReshare() + var interactions = this.model.interactions + + return _.extend(this.defaultPresenter(),{ + commentsCount : interactions.commentsCount(), + likesCount : interactions.likesCount(), + resharesCount : interactions.resharesCount(), + userCanReshare : interactions.userCanReshare(), + userLike : interactions.userLike(), + userReshare : interactions.userReshare(), }) }, toggleLike: function(evt) { if(evt) { evt.preventDefault(); } - this.model.toggleLike(); + this.model.interactions.toggleLike(); }, resharePost : function(evt) { if(evt) { evt.preventDefault(); } if(!window.confirm(Diaspora.I18n.t("reshares.post", {name: this.model.reshareAuthor().name}))) { return } - var reshare = this.model.reshare() - var model = this.model - - reshare.save({}, { - url: this.model.createReshareUrl, - success : function(resp){ - var flash = new Diaspora.Widgets.FlashMessages; - flash.render({ - success: true, - notice: Diaspora.I18n.t("reshares.successful") - }); - model.trigger("interacted") - } - }); - }, - - userCanReshare : function() { - var isReshare = this.model.get("post_type") == "Reshare" - var rootExists = (isReshare ? this.model.get("root") : true) - - var publicPost = this.model.get("public"); - var userIsNotAuthor = this.model.get("author").diaspora_id != app.currentUser.get("diaspora_id"); - var userIsNotRootAuthor = rootExists && (isReshare ? this.model.get("root").author.diaspora_id != app.currentUser.get("diaspora_id") : true) - - return publicPost && app.currentUser.authenticated() && userIsNotAuthor && userIsNotRootAuthor; + this.model.interactions.reshare(); } }); diff --git a/app/assets/javascripts/app/views/likes_info_view.js b/app/assets/javascripts/app/views/likes_info_view.js index 5069f8946f448eda5e09fc307569de8b33443e4a..f4f3071bb5d5f3235ce2679fcb9210bff51defd3 100644 --- a/app/assets/javascripts/app/views/likes_info_view.js +++ b/app/assets/javascripts/app/views/likes_info_view.js @@ -10,23 +10,19 @@ app.views.LikesInfo = app.views.StreamObject.extend({ tooltipSelector : ".avatar", initialize : function() { - this.model.bind('expandedLikes', this.render, this) + this.model.interactions.bind('change', this.render, this) }, presenter : function() { return _.extend(this.defaultPresenter(), { - likes : this.model.likes.models + likes : this.model.interactions.likes.toJSON(), + likesCount : this.model.interactions.likesCount(), + likes_fetched : this.model.interactions.get("fetched"), }) }, showAvatars : function(evt){ if(evt) { evt.preventDefault() } - var self = this; - this.model.likes.fetch() - .done(function(resp){ - // set like attribute and like collection - self.model.set({likes : self.model.likes.reset(resp)}) - self.model.trigger("expandedLikes") - }) + this.model.interactions.fetch() } }); diff --git a/app/assets/javascripts/app/views/post-viewer/feedback.js b/app/assets/javascripts/app/views/post-viewer/feedback.js index 7f8584ba419a8295a95191afe6c0ba8d3d56e4b3..de63cbbb16c28404d5f20e6634dea180c598d11f 100644 --- a/app/assets/javascripts/app/views/post-viewer/feedback.js +++ b/app/assets/javascripts/app/views/post-viewer/feedback.js @@ -18,6 +18,11 @@ app.views.PostViewerFeedback = app.views.Feedback.extend({ tooltipSelector : ".label, .home-button", + initialize : function(){ + this.model.interactions.on("change", this.render, this) + }, + + postRenderTemplate : function() { this.sneakyVisiblity() }, @@ -36,5 +41,4 @@ app.views.PostViewerFeedback = app.views.Feedback.extend({ alert("you must be logged in to do that!") return false; } - }); \ No newline at end of file diff --git a/app/assets/javascripts/app/views/post-viewer/interactions.js b/app/assets/javascripts/app/views/post-viewer/interactions.js index a0b17600b43c18d8477f0886f2923f09ea7a8654..7d78f72646131441b3dcdd778c7d0da6dd04ac07 100644 --- a/app/assets/javascripts/app/views/post-viewer/interactions.js +++ b/app/assets/javascripts/app/views/post-viewer/interactions.js @@ -5,7 +5,8 @@ app.views.PostViewerInteractions = app.views.Base.extend({ subviews : { "#post-feedback" : "feedbackView", "#post-reactions" : "reactionsView", - "#new-post-comment" : "newCommentView" + "#new-post-comment" : "newCommentView", + ".interaction_counts" : "interactionCountsView" }, templateName: "post-viewer/interactions", @@ -18,7 +19,7 @@ app.views.PostViewerInteractions = app.views.Base.extend({ }, initViews : function() { - this.reactionsView = new app.views.PostViewerReactions({ model : this.model }) + this.reactionsView = new app.views.PostViewerReactions({ model : this.model.interactions }) /* subviews that require user */ this.feedbackView = new app.views.PostViewerFeedback({ model : this.model }) diff --git a/app/assets/javascripts/app/views/post-viewer/new_comment.js b/app/assets/javascripts/app/views/post-viewer/new_comment.js index 16775a80c340914960718c5c736f81966f03f64c..f31da66ca4a7f8929bf576f643c93a1bda55144c 100644 --- a/app/assets/javascripts/app/views/post-viewer/new_comment.js +++ b/app/assets/javascripts/app/views/post-viewer/new_comment.js @@ -10,7 +10,7 @@ app.views.PostViewerNewComment = app.views.Base.extend({ scrollableArea : "#post-reactions", initialize : function(){ - this.model.comments.bind("sync", this.clearAndReactivateForm, this) + this.model.interactions.comments.bind("sync", this.clearAndReactivateForm, this) }, postRenderTemplate : function() { @@ -25,7 +25,6 @@ app.views.PostViewerNewComment = app.views.Base.extend({ }, clearAndReactivateForm : function() { - this.model.trigger("interacted") this.toggleFormState() this.$("textarea").val("") .css('height', '18px') diff --git a/app/assets/javascripts/app/views/post-viewer/reactions.js b/app/assets/javascripts/app/views/post-viewer/reactions.js index 145ed1b02a18a59655214de468eef2ecf969e317..1447bde224c249355fef95afb00726935252144b 100644 --- a/app/assets/javascripts/app/views/post-viewer/reactions.js +++ b/app/assets/javascripts/app/views/post-viewer/reactions.js @@ -7,7 +7,16 @@ app.views.PostViewerReactions = app.views.Base.extend({ tooltipSelector : ".avatar", initialize : function() { - this.model.bind('interacted', this.render, this); + this.model.on('change', this.render, this); + this.model.comments.bind("add", this.appendComment, this) + }, + + presenter : function(){ + return { + likes : this.model.likes.toJSON(), + comments : this.model.comments.toJSON(), + reshares : this.model.reshares.toJSON() + } }, postRenderTemplate : function() { @@ -21,14 +30,15 @@ app.views.PostViewerReactions = app.views.Base.extend({ /* copy pasta from commentStream */ appendComment: function(comment) { - // Set the post as the comment's parent, so we can check - // on post ownership in the Comment view. - comment.set({parent : this.model.toJSON()}) + // Set the post as the comment's parent, so we can check on post ownership in the Comment view. + // model was post on old view, is interactions on new view + + var parent = this.model.get("post_type") ? this.model.toJSON : this.model.post.toJSON() + comment.set({parent : parent}) this.$("#post-comments").append(new app.views.Comment({ model: comment, className : "post-comment media" }).render().el); } - }); \ No newline at end of file diff --git a/app/assets/templates/comment-stream.jst.hbs b/app/assets/templates/comment-stream.jst.hbs index 200235fdaff9c73512c0c17a558ea8e430cbcf7a..a41b612d9f814620c42cd6cbde493752db84ca39 100644 --- a/app/assets/templates/comment-stream.jst.hbs +++ b/app/assets/templates/comment-stream.jst.hbs @@ -11,7 +11,7 @@ <div class="comments"> </div> {{#if loggedIn}} - <div class="comment no-border media new_comment_form_wrapper {{#unless comments_count}} hidden {{/unless}}"> + <div class="comment no-border media new_comment_form_wrapper {{#unless commentsCount}} hidden {{/unless}}"> {{#with current_user}} <a href="/people/{{guid}}" class="img"> {{{personImage this}}} diff --git a/app/assets/templates/feedback.jst.hbs b/app/assets/templates/feedback.jst.hbs index f22facbd23167b1b7f7a0bef5e6928fed5532a6a..bdf99a2af3611573f2ec7f4deb50bf875a2c3bc9 100644 --- a/app/assets/templates/feedback.jst.hbs +++ b/app/assets/templates/feedback.jst.hbs @@ -15,7 +15,7 @@ <a href="#" class="like_action" rel='nofollow'> - {{#if user_like}} + {{#if userLike}} {{t "stream.unlike"}} {{else}} {{t "stream.like"}} diff --git a/app/assets/templates/likes-info.jst.hbs b/app/assets/templates/likes-info.jst.hbs index 6a833be78c5e0f8596a913c2749ce2789459b871..6820ffb0547857745af3704026a0e277367963c3 100644 --- a/app/assets/templates/likes-info.jst.hbs +++ b/app/assets/templates/likes-info.jst.hbs @@ -1,4 +1,4 @@ -{{#if likes_count}} +{{#if likesCount}} <div class="comment"> <div class="media"> <span class="img"> @@ -6,21 +6,20 @@ </span> <div class="bd"> - {{#unless likes.length}} + {{#unless likes_fetched}} <a href="#" class="expand_likes grey"> - {{t "stream.likes" count=likes_count}} + {{t "stream.likes" count=likesCount}} </a> {{else}} {{#each likes}} - {{#with attributes.author}} + {{#with author}} <a href="/people/{{guid}}"> <img src="{{avatar.small}}" class="avatar micro" title="{{name}}"/> </a> {{/with}} {{/each}} - {{/unless}} </div> </div> diff --git a/app/assets/templates/post-viewer/feedback.jst.hbs b/app/assets/templates/post-viewer/feedback.jst.hbs index 742d052c59e4afbe2c718b0ce44084298f1af020..823db8cf92c40825ca5ece33e18c8830661e91ae 100644 --- a/app/assets/templates/post-viewer/feedback.jst.hbs +++ b/app/assets/templates/post-viewer/feedback.jst.hbs @@ -1,35 +1,35 @@ -<a href="#" rel="auth-required" class="label like" title="{{#if user_like}} {{t "viewer.unlike"}} {{else}} {{t "viewer.like"}} {{/if}}"> - {{#if user_like}} +<a href="#" rel="auth-required" class="label like" title="{{#if userLike}} {{t "viewer.unlike"}} {{else}} {{t "viewer.like"}} {{/if}}"> + {{#if userLike}} <i class="icon-heart icon-red"></i> {{else}} <i class="icon-heart icon-white"></i> {{/if}} - {{likes_count}} + {{likesCount}} </a> {{#if userCanReshare}} - <a href="#" rel="auth-required" class="label reshare" title="{{#if user_reshare}} {{t "viewer.reshared"}} {{else}} {{t "viewer.reshare"}} {{/if}}"> - {{#if user_reshare}} + <a href="#" rel="auth-required" class="label reshare" title="{{#if userReshare}} {{t "viewer.reshared"}} {{else}} {{t "viewer.reshare"}} {{/if}}"> + {{#if userReshare}} <i class="icon-retweet icon-blue"></i> {{else}} <i class="icon-retweet icon-white"></i> {{/if}} - {{reshares_count}} + {{resharesCount}} </a> {{else}} - <a class="label reshare-viewonly" title="{{#if user_reshare}} {{t "viewer.reshared"}} {{else}} {{t "viewer.reshare"}} {{/if}}"> - {{#if user_reshare}} + <a class="label reshare-viewonly" title="{{#if userReshare}} {{t "viewer.reshared"}} {{else}} {{t "viewer.reshare"}} {{/if}}"> + {{#if userReshare}} <i class="icon-retweet icon-blue"></i> {{else}} <i class="icon-retweet icon-white"></i> {{/if}} - {{reshares_count}} + {{resharesCount}} </a> {{/if}} <a href="#" class="label comment" rel="invoke-interaction-pane" title="{{t "viewer.comment"}}"> <i class="icon-comment icon-white"></i> - {{comments_count}} + {{commentsCount}} </a> <!-- this acts as a dock underlay --> diff --git a/app/assets/templates/small-frame.jst.hbs b/app/assets/templates/small-frame.jst.hbs index 51b7158b53349e28144fd9a9bc087bd7d40fa6d8..8c07e73107cea9ed578d6b5e1bb8e79181d9c9b4 100644 --- a/app/assets/templates/small-frame.jst.hbs +++ b/app/assets/templates/small-frame.jst.hbs @@ -38,9 +38,9 @@ <i class="icon-time timestamp" title="{{created_at}}" rel="tooltip"></i> <i class="icon-chevron-right permalink" title="View Post" rel="tooltip"></i> - <i class="icon-heart"></i> {{likes_count}} - <i class="icon-retweet"></i> {{reshares_count}} - <i class="icon-comment"></i> {{comments_count}} + <i class="icon-heart"></i> {{likesCount}} + <i class="icon-retweet"></i> {{resharesCount}} + <i class="icon-comment"></i> {{commentsCount}} </div> </div> diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 58b683405702374ecc539b325490364b651f118e..d8f43fc3d8342f0395b54283110282268161ccea 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -56,7 +56,7 @@ class CommentsController < ApplicationController @comments = @post.comments.for_a_stream respond_with do |format| - format.json { render :json => CommentPresenter.new(@comments), :status => 200 } + format.json { render :json => CommentPresenter.as_collection(@comments), :status => 200 } format.mobile{render :layout => false} end end diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb index 1eaf0faa159c2702fdbad7b989147332bf18e50a..1d99009f5e7b33b7a3e56d10789683e83a51662c 100644 --- a/app/controllers/likes_controller.rb +++ b/app/controllers/likes_controller.rb @@ -13,13 +13,13 @@ class LikesController < ApplicationController :json def create - @like = current_user.like!(target) if target + @like = current_user.like!(target) if target rescue ActiveRecord::RecordInvalid if @like respond_to do |format| format.html { render :nothing => true, :status => 201 } format.mobile { redirect_to post_path(@like.post_id) } - format.json { render :json => find_json_for_like, :status => 201 } + format.json { render :json => @like.as_api_response(:backbone), :status => 201 } end else render :nothing => true, :status => 422 @@ -27,32 +27,22 @@ class LikesController < ApplicationController end def destroy - @like = Like.where(:id => params[:id], :author_id => current_user.person.id).first + @like = Like.find_by_id_and_author_id!(params[:id], current_user.person.id) - if @like - current_user.retract(@like) - respond_to do |format| - format.json { render :json => find_json_for_like, :status => 202 } - end - else - respond_to do |format| - format.mobile { redirect_to :back } - format.json { render :nothing => true, :status => 403} - end + current_user.retract(@like) + respond_to do |format| + format.json { render :nothing => true, :status => 204 } end end + #I can go when the old stream goes. def index - if target - @likes = target.likes.includes(:author => :profile) - @people = @likes.map(&:author) + @likes = target.likes.includes(:author => :profile) + @people = @likes.map(&:author) - respond_to do |format| - format.all{ render :layout => false } - format.json{ render :json => @likes.as_api_response(:backbone) } - end - else - render :nothing => true, :status => 404 + respond_to do |format| + format.all { render :layout => false } + format.json { render :json => @likes.as_api_response(:backbone) } end end @@ -60,21 +50,11 @@ class LikesController < ApplicationController def target @target ||= if params[:post_id] - current_user.find_visible_shareable_by_id(Post, params[:post_id]) + current_user.find_visible_shareable_by_id(Post, params[:post_id]) || raise(ActiveRecord::RecordNotFound.new) else - comment = Comment.find(params[:comment_id]) - comment = nil unless current_user.find_visible_shareable_by_id(Post, comment.commentable_id) - comment - end - end - - def find_json_for_like - if @like.parent.is_a? Post - ExtremePostPresenter.new(@like.parent, current_user).as_json - elsif @like.parent.is_a? Comment - CommentPresenter.new(@like.parent) - else - @like.parent.respond_to?(:as_api_response) ? @like.parent.as_api_response(:backbone) : @like.parent.as_json + Comment.find(params[:comment_id]).tap do |comment| + raise(ActiveRecord::RecordNotFound.new) unless current_user.find_visible_shareable_by_id(Post, comment.commentable_id) + end end end end diff --git a/app/controllers/participations_controller.rb b/app/controllers/participations_controller.rb deleted file mode 100644 index 64336862106861b122563f0fd5e95bc0450e27bd..0000000000000000000000000000000000000000 --- a/app/controllers/participations_controller.rb +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -require Rails.root.join("app", "presenters", "post_presenter") - -class ParticipationsController < ApplicationController - include ApplicationHelper - before_filter :authenticate_user! - - respond_to :mobile, - :json - - def create - @participation = current_user.participate!(target) if target - - if @participation - respond_to do |format| - format.mobile { redirect_to post_path(@participation.post_id) } - format.json { render :json => ExtremePostPresenter.new(@participation.parent, current_user), :status => 201 } - end - else - render :nothing => true, :status => 422 - end - end - - def destroy - @participation = Participation.where(:id => params[:id], :author_id => current_user.person.id).first - - if @participation - current_user.retract(@participation) - respond_to do |format| - format.json { render :json => ExtremePostPresenter.new(@participation.parent, current_user), :status => 202 } - end - else - respond_to do |format| - format.mobile { redirect_to :back } - format.json { render :nothing => true, :status => 403} - end - end - end - - def index - if target - @participations = target.participations.includes(:author => :profile) - @people = @participations.map(&:author) - - respond_to do |format| - format.all{ render :layout => false } - format.json{ render :json => @participations.as_api_response(:backbone) } - end - else - render :nothing => true, :status => 404 - end - end - - protected - - def target - @target ||= if params[:post_id] - current_user.find_visible_shareable_by_id(Post, params[:post_id]) - else - comment = Comment.find(params[:comment_id]) - comment = nil unless current_user.find_visible_shareable_by_id(Post, comment.commentable_id) - comment - end - end -end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 711f2561d680ebd93fc5841fd98a7eb2f21d23c2..761028768c84841b1d117f1cb11565f166df9501 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -9,7 +9,7 @@ class PostsController < ApplicationController before_filter :authenticate_user!, :except => [:show, :iframe, :oembed] before_filter :set_format_if_malformed_from_status_net, :only => :show - before_filter :find_post, :only => [:show, :next, :previous] + before_filter :find_post, :only => [:show, :next, :previous, :interactions] layout 'post' @@ -25,15 +25,13 @@ class PostsController < ApplicationController end def show - return log_and_redirect_back unless @post - mark_corresponding_notification_read if user_signed_in? respond_to do |format| - format.html{ gon.post = ExtremePostPresenter.new(@post, current_user); render 'posts/show.html.haml' } + format.html{ gon.post = PostPresenter.new(@post, current_user); render 'posts/show.html.haml' } format.xml{ render :xml => @post.to_diaspora_xml } format.mobile{render 'posts/show.mobile.haml', :layout => "application"} - format.json{ render :json => ExtremePostPresenter.new(@post, current_user) } + format.json{ render :json => PostPresenter.new(@post, current_user) } end end @@ -43,7 +41,7 @@ class PostsController < ApplicationController def oembed post_id = OEmbedPresenter.id_from_url(params.delete(:url)) - post = find_by_guid_or_id_with_current_user(post_id) + post = Post.find_by_guid_or_id_with_user(post_id, current_user) if post.present? oembed = OEmbedPresenter.new(post, params.slice(:format, :maxheight, :minheight)) render :json => oembed @@ -52,72 +50,54 @@ class PostsController < ApplicationController end end - def destroy - @post = current_user.posts.where(:id => params[:id]).first - if @post - current_user.retract(@post) - respond_to do |format| - format.js {render 'destroy'} - format.json { render :nothing => true, :status => 204 } - format.all {redirect_to stream_path} - end - else - Rails.logger.info "event=post_destroy status=failure user=#{current_user.diaspora_handle} reason='User does not own post'" - render :nothing => true, :status => 404 - end - end - - def update - @post = current_user.posts.find(params[:id]) - if @post - @post.favorite = !@post.favorite - @post.save - render :nothing => true, :status => 202 - end - end - def next - next_post = visible_posts_from_author.newer(@post) + next_post = Post.visible_from_author(@post.author, current_user).newer(@post) respond_to do |format| format.html{ redirect_to post_path(next_post) } - format.json{ render :json => ExtremePostPresenter.new(next_post, current_user)} + format.json{ render :json => PostPresenter.new(next_post, current_user)} end end def previous - previous_post = visible_posts_from_author.older(@post) + previous_post = Post.visible_from_author(@post.author, current_user).older(@post) respond_to do |format| format.html{ redirect_to post_path(previous_post) } - format.json{ render :json => ExtremePostPresenter.new(previous_post, current_user)} + format.json{ render :json => PostPresenter.new(previous_post, current_user)} end end - protected + def interactions + respond_with(PostInteractionPresenter.new(@post, current_user)) + end + + def destroy + find_current_user_post(params[:id]) + current_user.retract(@post) - def log_and_redirect_back #preserving old functionality, but this should probably be removed - user_id = (user_signed_in? ? current_user : nil) - Rails.logger.info(":event => :link_to_nonexistent_post, :ref => #{request.env['HTTP_REFERER']}, :user_id => #{user_id}, :post_id => #{params[:id]}") - flash[:error] = I18n.t('posts.show.not_found') - redirect_to :back + respond_to do |format| + format.js { render 'destroy' } + format.json { render :nothing => true, :status => 204 } + format.all { redirect_to stream_path } + end end - def find_post - @post = find_by_guid_or_id_with_current_user(params[:id]) + def update + find_current_user_post(params[:id]) + @post.favorite = !@post.favorite + @post.save + render :nothing => true, :status => 202 end - def visible_posts_from_author - Post.visible_from_author(@post.author, current_user) + protected + + def find_post #checks whether current user can see it + @post = Post.find_by_guid_or_id_with_user(params[:id], current_user) end - def find_by_guid_or_id_with_current_user(id) - key = id.to_s.length <= 8 ? :id : :guid - if user_signed_in? - current_user.find_visible_shareable_by_id(Post, id, :key => key) - else - Post.where(key => id, :public => true).includes(:author, :comments => :author).first - end + def find_current_user_post(id) #makes sure current_user can modify + @post = current_user.posts.find(id) end def set_format_if_malformed_from_status_net diff --git a/app/models/post.rb b/app/models/post.rb index 1dc27a93d2f84950aeea0e150a228b4e28ce6e81..6e24533a7f28d57a20c938131a15936898759ab6 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -143,4 +143,15 @@ class Post < ActiveRecord::Base def nsfw self.author.profile.nsfw? end + + def self.find_by_guid_or_id_with_user(id, user=nil) + key = id.to_s.length <= 8 ? :id : :guid + post = if user + user.find_visible_shareable_by_id(Post, id, :key => key) + else + Post.where(key => id, :public => true).includes(:author, :comments => :author).first + end + + post || raise(ActiveRecord::RecordNotFound.new("could not find a post with id #{id}")) + end end diff --git a/app/presenters/extreme_post_presenter.rb b/app/presenters/extreme_post_presenter.rb index 49d872a185da22ab0f4ad42bd85326e8dd0f4e65..c35b54088476a7d27514c47b61e8a11a87f64dd2 100644 --- a/app/presenters/extreme_post_presenter.rb +++ b/app/presenters/extreme_post_presenter.rb @@ -9,6 +9,6 @@ class ExtremePostPresenter def as_json(options={}) post = PostPresenter.new(@post, @current_user) interactions = PostInteractionPresenter.new(@post, @current_user) - post.as_json.merge!(interactions.as_json) + post.as_json.merge!(:interactions => interactions.as_json) end end \ No newline at end of file diff --git a/app/presenters/last_three_comments_decorator.rb b/app/presenters/last_three_comments_decorator.rb index 884d0dc4916332eb1e5c8055568473cbe61521ed..04e1c91b344c197685631b5a3e192571d6105633 100644 --- a/app/presenters/last_three_comments_decorator.rb +++ b/app/presenters/last_three_comments_decorator.rb @@ -4,6 +4,8 @@ class LastThreeCommentsDecorator end def as_json(options={}) - @presenter.as_json.merge({:last_three_comments => CommentPresenter.as_collection(@presenter.post.last_three_comments)}) + @presenter.as_json.tap do |post| + post[:interactions].merge!(:comments => CommentPresenter.as_collection(@presenter.post.last_three_comments)) + end end end \ No newline at end of file diff --git a/app/presenters/post_presenter.rb b/app/presenters/post_presenter.rb index 40b2fb95422e45763482d7650f4da649083b8114..151f51176d6b2d6c3417ac169b0dfb5c669bc0f2 100644 --- a/app/presenters/post_presenter.rb +++ b/app/presenters/post_presenter.rb @@ -20,9 +20,6 @@ class PostPresenter :public => @post.public, :created_at => @post.created_at, :interacted_at => @post.interacted_at, - :comments_count => @post.comments_count, - :likes_count => @post.likes_count, - :reshares_count => @post.reshares_count, :provider_display_name => @post.provider_display_name, :post_type => @post.post_type, :image_url => @post.image_url, @@ -38,8 +35,14 @@ class PostPresenter :title => title, :next_post => next_post_path, :previous_post => previous_post_path, - :user_like => user_like, - :user_reshare => user_reshare + + :interactions => { + :likes => [user_like].compact, + :reshares => [user_reshare].compact, + :comments_count => @post.comments_count, + :likes_count => @post.likes_count, + :reshares_count => @post.reshares_count, + } } end diff --git a/config/routes.rb b/config/routes.rb index 9944d9e84b8d8c3b930d5667d144b92b9ba14ece..249eea6e048331a36e4eaf9e5743772381f8869e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,8 +17,10 @@ Diaspora::Application.routes.draw do member do get :next get :previous + get :interactions end - resources :likes, :only => [:create, :destroy, :index] + + resources :likes, :only => [:create, :destroy, :index ] resources :participations, :only => [:create, :destroy, :index] resources :comments, :only => [:new, :create, :destroy, :index] end diff --git a/features/post_viewer.feature b/features/post_viewer.feature index 7841367591d217f250720a2061ea4a394590809f..e099c3c9785d62dddc26fac5ce50b2e926d2e2c7 100644 --- a/features/post_viewer.feature +++ b/features/post_viewer.feature @@ -7,9 +7,10 @@ Feature: Post Viewer Background: Given a user with email "alice@alice.com" And I sign in as "alice@alice.com" - - @wip - Scenario: Paging through posts - Given I have posts for each type of template - Then I visit all of my posts - And I should have seen all of my posts displayed with the correct template + +# Wip tag sad on new cucumber, commenting for now. +# @wip +# Scenario: Paging through posts +# Given I have posts for each type of template +# Then I visit all of my posts +# And I should have seen all of my posts displayed with the correct template diff --git a/lib/federated/generator.rb b/lib/federated/generator.rb index ba90546dbf398a3e5b32250edb8fc28a76de1981..96fe9006c640729bd09d4c9f80e03a5feb41d4ab 100644 --- a/lib/federated/generator.rb +++ b/lib/federated/generator.rb @@ -7,7 +7,7 @@ module Federated def create!(options={}) relayable = build(options) - if relayable.save + if relayable.save! FEDERATION_LOGGER.info("user:#{@user.id} dispatching #{relayable.class}:#{relayable.guid}") Postzord::Dispatcher.defer_build_and_post(@user, relayable) relayable diff --git a/spec/controllers/likes_controller_spec.rb b/spec/controllers/likes_controller_spec.rb index 1b91262ca0f7f9f198c1c12c95a15ed7ffd489fe..8b27516b9f73665c74f3693ccaea5901a9299a59 100644 --- a/spec/controllers/likes_controller_spec.rb +++ b/spec/controllers/likes_controller_spec.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is + # Copyright (c) 2010-2011, Diaspora Inc. This file is # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. @@ -88,7 +88,7 @@ describe LikesController do it 'returns a 404 for a post not visible to the user' do sign_in eve - get :index, id_field => @message.id + expect{get :index, id_field => @message.id}.to raise_error(ActiveRecord::RecordNotFound) end it 'returns an array of likes for a post' do @@ -114,22 +114,19 @@ describe LikesController do expect { delete :destroy, :format => :json, id_field => @like.target_id, :id => @like.id }.should change(Like, :count).by(-1) - response.status.should == 202 + response.status.should == 204 end it 'does not let a user destroy other likes' do like2 = eve.like!(@message) + like_count = Like.count expect { delete :destroy, :format => :json, id_field => like2.target_id, :id => like2.id - }.should_not change(Like, :count) + }.should raise_error(ActiveRecord::RecordNotFound) - response.status.should == 403 - end + Like.count.should == like_count - it 'returns the parent post presenter' do - delete :destroy, :format => :json, id_field => @like.target_id, :id => @like.id - response.body.should include 'post' if class_const != Comment end end end diff --git a/spec/controllers/participations_controller_spec.rb b/spec/controllers/participations_controller_spec.rb deleted file mode 100644 index 63970b7a189616d33f15826e4948237ea833bf69..0000000000000000000000000000000000000000 --- a/spec/controllers/participations_controller_spec.rb +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -require 'spec_helper' - -describe ParticipationsController do - before do - @alices_aspect = alice.aspects.where(:name => "generic").first - @bobs_aspect = bob.aspects.where(:name => "generic").first - - sign_in :user, alice - end - - context "Posts" do - let(:id_field){ "post_id" } - - describe '#create' do - let(:participation_hash) { - { id_field => "#{@target.id}", - :format => :json} - } - let(:disparticipation_hash) { - { id_field => "#{@target.id}", - :format => :json } - } - - context "on my own post" do - it 'succeeds' do - @target = alice.post :status_message, :text => "AWESOME", :to => @alices_aspect.id - post :create, participation_hash - response.code.should == '201' - end - end - - context "on a post from a contact" do - before do - @target = bob.post(:status_message, :text => "AWESOME", :to => @bobs_aspect.id) - end - - it 'participations' do - post :create, participation_hash - response.code.should == '201' - end - - it 'disparticipations' do - post :create, disparticipation_hash - response.code.should == '201' - end - - it "doesn't post multiple times" do - alice.participate!(@target) - post :create, disparticipation_hash - response.code.should == '422' - end - end - - context "on a post from a stranger" do - before do - @target = eve.post :status_message, :text => "AWESOME", :to => eve.aspects.first.id - end - - it "doesn't post" do - alice.should_not_receive(:participate!) - post :create, participation_hash - response.code.should == '422' - end - end - end - - describe '#index' do - before do - @message = alice.post(:status_message, :text => "hey", :to => @alices_aspect.id) - end - - it 'generates a jasmine fixture', :fixture => true do - get :index, id_field => @message.id, :format => :json - - save_fixture(response.body, "ajax_participations_on_posts") - end - - it 'returns a 404 for a post not visible to the user' do - sign_in eve - get :index, id_field => @message.id, :format => :json - end - - it 'returns an array of participations for a post' do - bob.participate!(@message) - get :index, id_field => @message.id, :format => :json - assigns[:participations].map(&:id).should == @message.participation_ids - end - - it 'returns an empty array for a post with no participations' do - get :index, id_field => @message.id, :format => :json - assigns[:participations].should == [] - end - end - - describe '#destroy' do - before do - @message = bob.post(:status_message, :text => "hey", :to => @alices_aspect.id) - @participation = alice.participate!(@message) - end - - it 'lets a user destroy their participation' do - expect { - delete :destroy, :format => :json, id_field => @participation.target_id, :id => @participation.id - }.should change(Participation, :count).by(-1) - response.status.should == 202 - end - - it 'does not let a user destroy other participations' do - participation2 = eve.participate!(@message) - - expect { - delete :destroy, :format => :json, id_field => participation2.target_id, :id => participation2.id - }.should_not change(Participation, :count) - - response.status.should == 403 - end - - it 'returns the parent post presenter' do - delete :destroy, :format => :json, id_field => @participation.target_id, :id => @participation.id - response.body.should include 'post' - end - end - end -end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index 5fb918bf3a21f00ff6cc35d2bf826da588de7384..ad74d2d9a60e71d689b7c643ab087231e944df85 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -55,9 +55,8 @@ describe PostsController do response.should be_success end - it 'redirects if the post is missing' do - get :show, :id => 1234567 - response.should be_redirect + it '404 if the post is missing' do + expect { get :show, :id => 1234567 }.to raise_error(ActiveRecord::RecordNotFound) end end @@ -86,8 +85,7 @@ describe PostsController do it 'does not show a private post' do status = alice.post(:status_message, :text => "hello", :public => false, :to => 'all') - get :show, :id => status.id - response.status = 302 + expect { get :show, :id => status.id }.to raise_error(ActiveRecord::RecordNotFound) end # We want to be using guids from now on for this post route, but do not want to break @@ -128,8 +126,7 @@ describe PostsController do end it 'returns a 404 response when the post is not found' do - get :oembed, :url => "/posts/#{@message.id}" - response.should_not be_success + expect { get :oembed, :url => "/posts/#{@message.id}" }.to raise_error(ActiveRecord::RecordNotFound) end end @@ -155,15 +152,13 @@ describe PostsController do it 'will not let you destroy posts visible to you' do message = bob.post(:status_message, :text => "hey", :to => bob.aspects.first.id) - delete :destroy, :format => :js, :id => message.id - response.should_not be_success + expect { delete :destroy, :format => :js, :id => message.id }.to raise_error(ActiveRecord::RecordNotFound) StatusMessage.exists?(message.id).should be_true end it 'will not let you destory posts you do not own' do message = eve.post(:status_message, :text => "hey", :to => eve.aspects.first.id) - delete :destroy, :format => :js, :id => message.id - response.should_not be_success + expect { delete :destroy, :format => :js, :id => message.id }.to raise_error(ActiveRecord::RecordNotFound) StatusMessage.exists?(message.id).should be_true end end @@ -171,8 +166,8 @@ describe PostsController do describe "#next" do before do sign_in alice - #lets make a class and unit test it, because this is still not working - @controller.stub_chain(:visible_posts_from_author, :newer).and_return(next_post) + Post.stub(:find_by_guid_or_id_with_user).and_return(mock_model(Post, :author => 4)) + Post.stub_chain(:visible_from_author, :newer).and_return(next_post) end let(:next_post){ mock_model(StatusMessage, :id => 34)} @@ -181,7 +176,7 @@ describe PostsController do let(:mock_presenter) { mock(:as_json => {:title => "the unbearable lightness of being"}) } it "should return a show presenter the next post" do - ExtremePostPresenter.should_receive(:new).with(next_post, alice).and_return(mock_presenter) + PostPresenter.should_receive(:new).with(next_post, alice).and_return(mock_presenter) get :next, :id => 14, :format => :json response.body.should == {:title => "the unbearable lightness of being"}.to_json end @@ -198,8 +193,8 @@ describe PostsController do describe "previous" do before do sign_in alice - #lets make a class and unit test it, because this is still not working - @controller.stub_chain(:visible_posts_from_author, :older).and_return(previous_post) + Post.stub(:find_by_guid_or_id_with_user).and_return(mock_model(Post, :author => 4)) + Post.stub_chain(:visible_from_author, :older).and_return(previous_post) end let(:previous_post){ mock_model(StatusMessage, :id => 11)} @@ -208,7 +203,7 @@ describe PostsController do let(:mock_presenter) { mock(:as_json => {:title => "existential crises"})} it "should return a show presenter the next post" do - ExtremePostPresenter.should_receive(:new).with(previous_post, alice).and_return(mock_presenter) + PostPresenter.should_receive(:new).with(previous_post, alice).and_return(mock_presenter) get :previous, :id => 14, :format => :json response.body.should == {:title => "existential crises"}.to_json end diff --git a/spec/javascripts/app/models/post/interacations_spec.js b/spec/javascripts/app/models/post/interacations_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f6a5be3ad6d92f150e1e26bb065e7b1796a18786 --- /dev/null +++ b/spec/javascripts/app/models/post/interacations_spec.js @@ -0,0 +1,45 @@ +describe("app.models.Post.Interactions", function(){ + beforeEach(function(){ + this.interactions = factory.post() + this.interactions = this.interactions.interactions + this.author = factory.author({guid: "loggedInAsARockstar"}) + loginAs({guid: "loggedInAsARockstar"}) + + this.userLike = new app.models.Like({author : this.author}) + }) + + describe("toggleLike", function(){ + it("calls unliked when the user_like exists", function(){ + this.interactions.likes.add(this.userLike) + spyOn(this.interactions, "unlike").andReturn(true); + this.interactions.toggleLike(); + expect(this.interactions.unlike).toHaveBeenCalled(); + }) + + it("calls liked when the user_like does not exist", function(){ + this.interactions.likes.reset([]); + spyOn(this.interactions, "like").andReturn(true); + this.interactions.toggleLike(); + expect(this.interactions.like).toHaveBeenCalled(); + }) + }) + + describe("like", function(){ + it("calls create on the likes collection", function(){ + spyOn(this.interactions.likes, "create"); + + this.interactions.like(); + expect(this.interactions.likes.create).toHaveBeenCalled(); + }) + }) + + describe("unlike", function(){ + it("calls destroy on the likes collection", function(){ + this.interactions.likes.add(this.userLike) + spyOn(this.userLike, "destroy"); + + this.interactions.unlike(); + expect(this.userLike.destroy).toHaveBeenCalled(); + }) + }) +}) \ No newline at end of file diff --git a/spec/javascripts/app/models/post_spec.js b/spec/javascripts/app/models/post_spec.js index 90dbca87cca0b3368f37bb04ffe5e14b2b16480f..c6f6ef14b81aef32cd350d83b6c7f76d7084e02b 100644 --- a/spec/javascripts/app/models/post_spec.js +++ b/spec/javascripts/app/models/post_spec.js @@ -36,43 +36,4 @@ describe("app.models.Post", function() { expect(this.post.createdAt()).toEqual(+date); }); }); - - describe("toggleLike", function(){ - it("calls unliked when the user_like exists", function(){ - this.post.set({user_like : "123"}); - spyOn(this.post, "unlike").andReturn(true); - - this.post.toggleLike(); - expect(this.post.unlike).toHaveBeenCalled(); - }) - - it("calls liked when the user_like does not exist", function(){ - this.post.set({user_like : null}); - spyOn(this.post, "like").andReturn(true); - - this.post.toggleLike(); - expect(this.post.like).toHaveBeenCalled(); - }) - }) - - describe("like", function(){ - it("calls create on the likes collection", function(){ - spyOn(this.post.likes, "create"); - - this.post.like(); - expect(this.post.likes.create).toHaveBeenCalled(); - }) - }) - - describe("unlike", function(){ - it("calls destroy on the likes collection", function(){ - var like = new app.models.Like(); - this.post.set({user_like : like.toJSON()}) - - spyOn(app.models.Like.prototype, "destroy"); - - this.post.unlike(); - expect(app.models.Like.prototype.destroy).toHaveBeenCalled(); - }) - }) }); diff --git a/spec/javascripts/app/views/comment_stream_view_spec.js b/spec/javascripts/app/views/comment_stream_view_spec.js index bcb72a470ca76c75e18bbf4f83b7b25815b5f00c..f808a43df3fb2c4bbd7edca8335688b25fb32e28 100644 --- a/spec/javascripts/app/views/comment_stream_view_spec.js +++ b/spec/javascripts/app/views/comment_stream_view_spec.js @@ -29,28 +29,6 @@ describe("app.views.CommentStream", function(){ }) }) - describe("createComment", function(){ - beforeEach(function(){ - spyOn(this.view.model.comments, "create") - }) - - it("clears the new comment textarea", function(){ - var comment = { - "id": 1234, - "text": "hey", - "author": "not_null" - }; - spyOn($, "ajax").andCallFake(function(params) { - params.success(comment); - }); - - $(this.view.el).html($("<textarea/>", {"class" : 'comment_box'}).val(comment.text)) - this.view.createComment() - expect(this.view.$(".comment_box").val()).toBe("") - expect(this.view.model.comments.create).toHaveBeenCalled() - }) - }) - describe("appendComment", function(){ it("appends this.model as 'parent' to the comment", function(){ var comment = new app.models.Comment(factory.comment()) diff --git a/spec/javascripts/app/views/feedback_view_spec.js b/spec/javascripts/app/views/feedback_view_spec.js index 223a41e3765a8a45fbc8ed7db3c430d771163239..3c6b9ee5c4518ac7eac3eb2efbb37723faeb87e4 100644 --- a/spec/javascripts/app/views/feedback_view_spec.js +++ b/spec/javascripts/app/views/feedback_view_spec.js @@ -19,7 +19,7 @@ describe("app.views.Feedback", function(){ describe("triggers", function() { it('re-renders when the model triggers feedback', function(){ spyOn(this.view, "postRenderTemplate") - this.view.model.trigger("interacted") + this.view.model.interactions.trigger("change") expect(this.view.postRenderTemplate).toHaveBeenCalled() }) }) @@ -32,15 +32,17 @@ describe("app.views.Feedback", function(){ context("likes", function(){ it("calls 'toggleLike' on the target post", function(){ + loginAs(this.post.interactions.likes.models[0].get("author")) this.view.render(); - spyOn(this.post, "toggleLike"); - + spyOn(this.post.interactions, "toggleLike"); this.link().click(); - expect(this.post.toggleLike).toHaveBeenCalled(); + expect(this.post.interactions.toggleLike).toHaveBeenCalled(); }) context("when the user likes the post", function(){ it("the like action should be 'Unlike'", function(){ + spyOn(this.post.interactions, "userLike").andReturn(factory.like()); + this.view.render() expect(this.link().text()).toContain(Diaspora.I18n.t('stream.unlike')) }) }) @@ -137,7 +139,7 @@ describe("app.views.Feedback", function(){ it("reshares the model", function(){ spyOn(window, "confirm").andReturn(true); - spyOn(this.view.model.reshare(), "save") + spyOn(this.view.model.reshare(), "save").andReturn(new $.Deferred) this.view.$(".reshare_action").first().click(); expect(this.view.model.reshare().save).toHaveBeenCalled(); }) diff --git a/spec/javascripts/app/views/likes_info_view_spec.js b/spec/javascripts/app/views/likes_info_view_spec.js index e018112453adc1a56ecd6855b851b59295602848..a7ffb548430c4c2b0ad0ae426a72834cc9673c43 100644 --- a/spec/javascripts/app/views/likes_info_view_spec.js +++ b/spec/javascripts/app/views/likes_info_view_spec.js @@ -16,34 +16,32 @@ describe("app.views.LikesInfo", function(){ describe(".render", function(){ it("displays a the like count if it is above zero", function() { + spyOn(this.view.model.interactions, "likesCount").andReturn(3); this.view.render(); - this.view.model.set({"likes_count" : 1}) - expect($(this.view.el).find(".expand_likes").length).toBe(1) }) it("does not display the like count if it is zero", function() { - this.post.save({likes_count : 0}); + spyOn(this.view.model.interactions, "likesCount").andReturn(0); this.view.render(); - expect($(this.view.el).html().trim()).toBe(""); }) it("fires on a model change", function(){ spyOn(this.view, "postRenderTemplate") - this.view.model.trigger('expandedLikes') + this.view.model.interactions.trigger('change') expect(this.view.postRenderTemplate).toHaveBeenCalled() }) }) describe("showAvatars", function(){ beforeEach(function(){ - spyOn(this.post.likes, "fetch").andCallThrough() + spyOn(this.post.interactions, "fetch").andCallThrough() }) it("calls fetch on the model's like collection", function(){ this.view.showAvatars(); - expect(this.post.likes.fetch).toHaveBeenCalled(); + expect(this.post.interactions.fetch).toHaveBeenCalled(); }) it("sets the fetched response to the model's likes", function(){ diff --git a/spec/javascripts/helpers/factory.js b/spec/javascripts/helpers/factory.js index fc836574060727d591af73ea6a45f9ade985431a..48adcd0cfbc8de6c3d9bafc0a081a95c9ee658af 100644 --- a/spec/javascripts/helpers/factory.js +++ b/spec/javascripts/helpers/factory.js @@ -57,20 +57,24 @@ factory = { "provider_display_name" : null, "created_at" : "2012-01-03T19:53:13Z", "interacted_at" : '2012-01-03T19:53:13Z', - "last_three_comments" : null, "public" : false, "guid" : this.guid(), "image_url" : null, "o_embed_cache" : null, "photos" : [], "text" : "jasmine is bomb", - "reshares_count" : 0, "id" : this.id.next(), "object_url" : null, "root" : null, "post_type" : "StatusMessage", - "likes_count" : 0, - "comments_count" : 0 + "interactions" : { + "reshares_count" : 0, + "likes_count" : 0, + "comments_count" : 0, + "comments" : [], + "likes" : [], + "reshares" : [] + } } }, diff --git a/spec/models/user/social_actions_spec.rb b/spec/models/user/social_actions_spec.rb index a9a88078b58d7f4d32515c9f4c40693fbd2f257e..33c99e6756e6642df91f31eaa734e7da80b417b0 100644 --- a/spec/models/user/social_actions_spec.rb +++ b/spec/models/user/social_actions_spec.rb @@ -77,9 +77,10 @@ describe User::SocialActions do it "does not allow multiple likes" do alice.like!(@status) - lambda { - alice.like!(@status) - }.should_not change(@status, :likes) + likes = @status.likes + expect { alice.like!(@status) }.to raise_error + + @status.reload.likes.should == likes end end end \ No newline at end of file