diff --git a/Changelog.md b/Changelog.md index bd1fc24a7..33951d6b1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -29,6 +29,7 @@ If so, please delete it since it will prevent the federation from working proper * Use id as fallback when sorting posts [#7523](https://github.com/diaspora/diaspora/pull/7523) * Remove no-posts-info when adding posts to the stream [#7523](https://github.com/diaspora/diaspora/pull/7523) * Upgrade to rails 5.1 [#7514](https://github.com/diaspora/diaspora/pull/7514) +* Refactoring single post view interactions [#7182](https://github.com/diaspora/diaspora/pull/7182) ## Bug fixes diff --git a/app/assets/javascripts/app/models/post.js b/app/assets/javascripts/app/models/post.js index c351ee787..b693d7b2d 100644 --- a/app/assets/javascripts/app/models/post.js +++ b/app/assets/javascripts/app/models/post.js @@ -48,12 +48,6 @@ app.models.Post = Backbone.Model.extend(_.extend({}, app.models.formatDateMixin, var body = this.get("text").trim() , newlineIdx = body.indexOf("\n"); return (newlineIdx > 0 ) ? body.substr(newlineIdx+1, body.length) : ""; - }, - - //returns a promise - preloadOrFetch : function(){ - var action = app.hasPreload("post") ? this.set(app.parsePreload("post")) : this.fetch(); - return $.when(action); } })); // @license-end diff --git a/app/assets/javascripts/app/models/post/interactions.js b/app/assets/javascripts/app/models/post/interactions.js index b3700b97d..5770b3291 100644 --- a/app/assets/javascripts/app/models/post/interactions.js +++ b/app/assets/javascripts/app/models/post/interactions.js @@ -3,10 +3,6 @@ //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}); @@ -14,33 +10,16 @@ app.models.Post.Interactions = Backbone.Model.extend({ 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"); + return this.get("likes_count"); }, resharesCount : function(){ - return this.get("fetched") ? this.reshares.models.length : this.get("reshares_count"); + return this.get("reshares_count"); }, commentsCount : function(){ - return this.get("fetched") ? this.comments.models.length : this.get("comments_count"); + return this.get("comments_count"); }, userLike : function(){ diff --git a/app/assets/javascripts/app/pages/single-post-viewer.js b/app/assets/javascripts/app/pages/single-post-viewer.js index c5a30f1c9..f12f45a33 100644 --- a/app/assets/javascripts/app/pages/single-post-viewer.js +++ b/app/assets/javascripts/app/pages/single-post-viewer.js @@ -9,9 +9,8 @@ app.pages.SinglePostViewer = app.views.Base.extend({ }, initialize : function() { - this.model = new app.models.Post({ id : gon.post.id }); - this.model.preloadOrFetch().done(_.bind(this.initViews, this)); - this.model.interactions.fetch(); //async, yo, might want to throttle this later. + this.model = new app.models.Post(gon.post); + this.initViews(); }, initViews : function() { diff --git a/app/assets/javascripts/app/router.js b/app/assets/javascripts/app/router.js index 287aef9a6..ab9e11a69 100644 --- a/app/assets/javascripts/app/router.js +++ b/app/assets/javascripts/app/router.js @@ -182,7 +182,7 @@ app.Router = Backbone.Router.extend({ }, singlePost: function(id) { - this.renderPage(function() { return new app.pages.SinglePostViewer({id: id}); }); + this.renderPage(function() { return new app.pages.SinglePostViewer({id: id, el: $("#container")}); }); }, spotlight: function() { diff --git a/app/assets/javascripts/app/views/single-post-viewer/single_post_comment_stream.js b/app/assets/javascripts/app/views/single-post-viewer/single_post_comment_stream.js index 49ddf11db..6981bdc3a 100644 --- a/app/assets/javascripts/app/views/single-post-viewer/single_post_comment_stream.js +++ b/app/assets/javascripts/app/views/single-post-viewer/single_post_comment_stream.js @@ -7,7 +7,9 @@ app.views.SinglePostCommentStream = app.views.CommentStream.extend({ this.CommentView = app.views.ExpandedComment; $(window).on('hashchange',this.highlightPermalinkComment); this.setupBindings(); - this.model.comments.on("reset", this.render, this); + this.model.comments.fetch({success: function() { + setTimeout(this.highlightPermalinkComment, 0); + }.bind(this)}); }, highlightPermalinkComment: function() { @@ -17,14 +19,13 @@ app.views.SinglePostCommentStream = app.views.CommentStream.extend({ $(".highlighted").removeClass("highlighted"); element.addClass("highlighted"); var pos = element.offset().top - headerSize; - window.scroll(0, pos); + $("html,body").animate({scrollTop: pos}); } }, postRenderTemplate: function() { app.views.CommentStream.prototype.postRenderTemplate.apply(this); this.$(".new-comment-form-wrapper").removeClass("hidden"); - _.defer(this.highlightPermalinkComment); }, presenter: function(){ diff --git a/app/assets/javascripts/app/views/single-post-viewer/single_post_interaction_counts.js b/app/assets/javascripts/app/views/single-post-viewer/single_post_interaction_counts.js index 42348627c..abc4f5d9e 100644 --- a/app/assets/javascripts/app/views/single-post-viewer/single_post_interaction_counts.js +++ b/app/assets/javascripts/app/views/single-post-viewer/single_post_interaction_counts.js @@ -4,8 +4,15 @@ app.views.SinglePostInteractionCounts = app.views.Base.extend({ templateName: "single-post-viewer/single-post-interaction-counts", tooltipSelector: ".avatar.micro", + events: { + "click #show-all-likes": "showAllLikes", + "click #show-all-reshares": "showAllReshares" + }, + initialize: function() { this.model.interactions.on("change", this.render, this); + this.model.interactions.likes.on("change", this.render, this); + this.model.interactions.reshares.on("change", this.render, this); }, presenter: function() { @@ -15,8 +22,28 @@ app.views.SinglePostInteractionCounts = app.views.Base.extend({ reshares: interactions.reshares.toJSON(), commentsCount: interactions.commentsCount(), likesCount: interactions.likesCount(), - resharesCount: interactions.resharesCount() + resharesCount: interactions.resharesCount(), + showMoreLikes: interactions.likes.length < interactions.likesCount(), + showMoreReshares: interactions.reshares.length < interactions.resharesCount() }; + }, + + _showAll: function(interactionType, models) { + this.$("#show-all-" + interactionType).addClass("hidden"); + this.$("#" + interactionType + " .loader").removeClass("hidden"); + models.fetch({success: function() { + models.trigger("change"); + }}); + }, + + showAllLikes: function(evt) { + evt.preventDefault(); + this._showAll("likes", this.model.interactions.likes); + }, + + showAllReshares: function(evt) { + evt.preventDefault(); + this._showAll("reshares", this.model.interactions.reshares); } }); // @license-end diff --git a/app/assets/stylesheets/single-post-view.scss b/app/assets/stylesheets/single-post-view.scss index 15fdbcbde..ceaabc4fa 100644 --- a/app/assets/stylesheets/single-post-view.scss +++ b/app/assets/stylesheets/single-post-view.scss @@ -152,5 +152,16 @@ .interaction-avatars { overflow: hidden; + + .author-name:focus, + .author-name:hover { + text-decoration: none; + } + + .loader { + height: $line-height-computed; + vertical-align: text-bottom; + width: $line-height-computed; + } } } diff --git a/app/assets/templates/single-post-viewer/single-post-interaction-counts_tpl.jst.hbs b/app/assets/templates/single-post-viewer/single-post-interaction-counts_tpl.jst.hbs index 221274487..dcf8cdd31 100644 --- a/app/assets/templates/single-post-viewer/single-post-interaction-counts_tpl.jst.hbs +++ b/app/assets/templates/single-post-viewer/single-post-interaction-counts_tpl.jst.hbs @@ -10,6 +10,12 @@ {{{personImage this "small" "micro"}}} {{/linkToAuthor}} {{/each}} + {{#if showMoreReshares}} + + + {{/if}} {{/if}} @@ -25,6 +31,12 @@ {{{personImage this "small" "micro"}}} {{/linkToAuthor}} {{/each}} + {{#if showMoreLikes}} + + + {{/if}} {{/if}} diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 8b5386d2b..9d6b3258c 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -22,11 +22,11 @@ class PostsController < ApplicationController presenter = PostPresenter.new(post, current_user) respond_to do |format| format.html do - gon.post = presenter + gon.post = presenter.with_initial_interactions render locals: {post: presenter} end format.mobile { render locals: {post: post} } - format.json { render json: presenter } + format.json { render json: presenter.with_interactions } end end @@ -39,16 +39,6 @@ class PostsController < ApplicationController head :not_found end - def interactions - respond_to do |format| - format.json { - post = post_service.find!(params[:id]) - render json: PostInteractionPresenter.new(post, current_user) - } - format.any { head :not_acceptable } - end - end - def mentionable respond_to do |format| format.json { diff --git a/app/controllers/reshares_controller.rb b/app/controllers/reshares_controller.rb index 846091398..d38f49f6a 100644 --- a/app/controllers/reshares_controller.rb +++ b/app/controllers/reshares_controller.rb @@ -7,7 +7,7 @@ class ResharesController < ApplicationController rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid render plain: I18n.t("reshares.create.error"), status: 422 else - render json: ExtremePostPresenter.new(reshare, current_user), status: 201 + render json: PostPresenter.new(reshare, current_user).with_interactions, status: 201 end def index diff --git a/app/presenters/extreme_post_presenter.rb b/app/presenters/extreme_post_presenter.rb deleted file mode 100644 index c35b54088..000000000 --- a/app/presenters/extreme_post_presenter.rb +++ /dev/null @@ -1,14 +0,0 @@ -#this file should go away, hence the name that is so full of lulz -#post interactions should probably be a decorator, and used in very few places... maybe? -class ExtremePostPresenter - def initialize(post, current_user) - @post = post - @current_user = current_user - end - - def as_json(options={}) - post = PostPresenter.new(@post, @current_user) - interactions = PostInteractionPresenter.new(@post, @current_user) - post.as_json.merge!(:interactions => interactions.as_json) - end -end \ No newline at end of file diff --git a/app/presenters/post_presenter.rb b/app/presenters/post_presenter.rb index 86064fbf9..a1187fba9 100644 --- a/app/presenters/post_presenter.rb +++ b/app/presenters/post_presenter.rb @@ -14,6 +14,20 @@ class PostPresenter < BasePresenter .merge(non_directly_retrieved_attributes) end + def with_interactions + interactions = PostInteractionPresenter.new(@post, current_user) + as_json.merge!(interactions: interactions.as_json) + end + + def with_initial_interactions + as_json.tap do |post| + post[:interactions].merge!( + likes: LikeService.new(current_user).find_for_post(@post.id).limit(30).as_api_response(:backbone), + reshares: ReshareService.new(current_user).find_for_post(@post.id).limit(30).as_api_response(:backbone) + ) + end + end + def metas_attributes { keywords: {name: "keywords", content: comma_separated_tags}, diff --git a/config/locales/javascript/javascript.en.yml b/config/locales/javascript/javascript.en.yml index 7c5071762..8f4ed6953 100644 --- a/config/locales/javascript/javascript.en.yml +++ b/config/locales/javascript/javascript.en.yml @@ -28,6 +28,7 @@ en: comma: "," edit: "Edit" no_results: "No results found" + show_all: "Show all" admins: dashboard: diff --git a/config/routes.rb b/config/routes.rb index b7d4c69ab..82d9afc91 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -29,7 +29,6 @@ Rails.application.routes.draw do resources :posts, only: %i(show destroy) do member do - get :interactions get :mentionable end diff --git a/spec/controllers/jasmine_fixtures/posts_spec.rb b/spec/controllers/jasmine_fixtures/posts_spec.rb new file mode 100644 index 000000000..aed5d118a --- /dev/null +++ b/spec/controllers/jasmine_fixtures/posts_spec.rb @@ -0,0 +1,11 @@ +require "spec_helper" + +describe PostsController, type: :controller do + describe "#show" do + it "generates the post_json fixture", fixture: true do + post = alice.post(:status_message, text: "hello world", public: true) + get :show, params: {id: post.id}, format: :json + save_fixture(response.body, "post_json") + end + end +end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index 28216a97e..2c5f72f92 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -129,38 +129,6 @@ describe PostsController, type: :controller do end end - describe "#interactions" do - context "user not signed in" do - it "returns a 401 for private posts and format json" do - get :interactions, params: {id: post.id}, format: :json - expect(response.status).to eq(401) - expect(JSON.parse(response.body)["error"]).to eq(I18n.t("devise.failure.unauthenticated")) - end - - it "returns a 406 for private posts and format html" do - get :interactions, params: {id: post.id} - expect(response.status).to eq(406) - end - end - - context "user signed in" do - before do - sign_in alice - end - - it "shows interactions of a post as json" do - get :interactions, params: {id: post.id}, format: :json - expect(response.body).to eq(PostInteractionPresenter.new(post, alice).to_json) - end - - it "returns a 406 for format html" do - sign_in alice - get :interactions, params: {id: post.id} - expect(response.status).to eq(406) - end - end - end - describe "#mentionable" do context "with a user signed in" do before do diff --git a/spec/javascripts/app/pages/single-post-viewer_spec.js b/spec/javascripts/app/pages/single-post-viewer_spec.js index df8f9c43c..c1db51394 100644 --- a/spec/javascripts/app/pages/single-post-viewer_spec.js +++ b/spec/javascripts/app/pages/single-post-viewer_spec.js @@ -1,6 +1,7 @@ describe("app.pages.SinglePostViewer", function(){ beforeEach(function() { - window.gon={};gon.post = {id: 42}; + window.gon = {}; + gon.post = $.parseJSON(spec.readFixture("post_json")); this.view = new app.pages.SinglePostViewer(); }); diff --git a/spec/javascripts/app/views/single-post-view/single_post_comment_stream_spec.js b/spec/javascripts/app/views/single-post-view/single_post_comment_stream_spec.js index 3e32ca3ee..28c80225d 100644 --- a/spec/javascripts/app/views/single-post-view/single_post_comment_stream_spec.js +++ b/spec/javascripts/app/views/single-post-view/single_post_comment_stream_spec.js @@ -9,14 +9,6 @@ describe("app.views.SinglePostCommentStream", function() { expect(this.view.CommentView).toBe(app.views.ExpandedComment); }); - it("calls render when the comments collection has been resetted", function() { - spyOn(app.views.SinglePostCommentStream.prototype, "render"); - this.view.initialize(); - expect(app.views.SinglePostCommentStream.prototype.render).not.toHaveBeenCalled(); - this.post.comments.reset(); - expect(app.views.SinglePostCommentStream.prototype.render).toHaveBeenCalled(); - }); - it("calls setupBindings", function() { spyOn(app.views.SinglePostCommentStream.prototype, "setupBindings"); this.view.initialize(); diff --git a/spec/javascripts/app/views/single-post-view/single_post_interaction_counts_spec.js b/spec/javascripts/app/views/single-post-view/single_post_interaction_counts_spec.js index 35abf7afe..0ff6959bc 100644 --- a/spec/javascripts/app/views/single-post-view/single_post_interaction_counts_spec.js +++ b/spec/javascripts/app/views/single-post-view/single_post_interaction_counts_spec.js @@ -1,6 +1,6 @@ describe("app.views.SinglePostInteractionCounts", function() { beforeEach(function() { - this.post = factory.post(); + this.post = factory.postWithInteractions(); this.view = new app.views.SinglePostInteractionCounts({model: this.post}); }); @@ -12,5 +12,151 @@ describe("app.views.SinglePostInteractionCounts", function() { this.post.interactions.trigger("change"); expect(app.views.SinglePostInteractionCounts.prototype.render).toHaveBeenCalled(); }); + + it("calls render when the likes change", function() { + spyOn(app.views.SinglePostInteractionCounts.prototype, "render"); + this.view.initialize(); + expect(app.views.SinglePostInteractionCounts.prototype.render).not.toHaveBeenCalled(); + this.post.interactions.likes.trigger("change"); + expect(app.views.SinglePostInteractionCounts.prototype.render).toHaveBeenCalled(); + }); + + it("calls render when the reshares change", function() { + spyOn(app.views.SinglePostInteractionCounts.prototype, "render"); + this.view.initialize(); + expect(app.views.SinglePostInteractionCounts.prototype.render).not.toHaveBeenCalled(); + this.post.interactions.reshares.trigger("change"); + expect(app.views.SinglePostInteractionCounts.prototype.render).toHaveBeenCalled(); + }); + }); + + describe("render", function() { + it("doesn't show a #show-all-likes link if there are no additional likes", function() { + this.view.render(); + expect(this.view.$("#show-all-likes").length).toBe(0); + }); + + it("shows a #show-all-likes link if there are additional likes", function() { + this.view.model.interactions.set("likes_count", this.view.model.interactions.likes.length + 1); + this.view.render(); + expect(this.view.$("#show-all-likes").length).toBe(1); + }); + + it("doesn't show a #show-all-reshares link if there are no additional reshares", function() { + this.view.render(); + expect(this.view.$("#show-all-reshares").length).toBe(0); + }); + + it("shows a #show-all-reshares link if there are additional reshares", function() { + this.view.model.interactions.set("reshares_count", this.view.model.interactions.reshares.length + 1); + this.view.render(); + expect(this.view.$("#show-all-reshares").length).toBe(1); + }); + }); + + describe("showAllLikes", function() { + it("is called when clicking #show-all-likes", function() { + spyOn(this.view, "showAllLikes"); + this.view.delegateEvents(); + this.view.model.interactions.set("likes_count", this.view.model.interactions.likes.length + 1); + this.view.render(); + expect(this.view.showAllLikes).not.toHaveBeenCalled(); + this.view.$("#show-all-likes").click(); + expect(this.view.showAllLikes).toHaveBeenCalled(); + }); + + it("calls _showAll", function() { + spyOn(this.view, "_showAll"); + this.view.showAllLikes($.Event()); + expect(this.view._showAll).toHaveBeenCalledWith("likes", this.view.model.interactions.likes); + }); + }); + + describe("showAllReshares", function() { + it("is called when clicking #show-all-reshares", function() { + spyOn(this.view, "showAllReshares"); + this.view.delegateEvents(); + this.view.model.interactions.set("reshares_count", this.view.model.interactions.reshares.length + 1); + this.view.render(); + expect(this.view.showAllReshares).not.toHaveBeenCalled(); + this.view.$("#show-all-reshares").click(); + expect(this.view.showAllReshares).toHaveBeenCalled(); + }); + + it("calls _showAll", function() { + spyOn(this.view, "_showAll"); + this.view.showAllReshares($.Event()); + expect(this.view._showAll).toHaveBeenCalledWith("reshares", this.view.model.interactions.reshares); + }); + }); + + describe("_showAll", function() { + beforeEach(function() { + this.view.model.interactions.set("likes_count", this.view.model.interactions.likes.length + 1); + this.view.model.interactions.set("reshares_count", this.view.model.interactions.reshares.length + 1); + this.view.render(); + }); + + context("with likes", function() { + it("hides the #show-all-likes link", function() { + expect(this.view.$("#show-all-likes")).not.toHaveClass("hidden"); + expect(this.view.$("#show-all-reshares")).not.toHaveClass("hidden"); + this.view._showAll("likes", this.view.model.interactions.likes); + expect(this.view.$("#show-all-likes")).toHaveClass("hidden"); + expect(this.view.$("#show-all-reshares")).not.toHaveClass("hidden"); + }); + + it("shows the likes loader", function() { + expect(this.view.$("#likes .loader")).toHaveClass("hidden"); + expect(this.view.$("#reshares .loader")).toHaveClass("hidden"); + this.view._showAll("likes", this.view.model.interactions.likes); + expect(this.view.$("#likes .loader")).not.toHaveClass("hidden"); + expect(this.view.$("#reshares .loader")).toHaveClass("hidden"); + }); + + it("calls #fetch on the model", function() { + spyOn(this.view.model.interactions.likes, "fetch"); + this.view._showAll("likes", this.view.model.interactions.likes); + expect(this.view.model.interactions.likes.fetch).toHaveBeenCalled(); + }); + + it("triggers 'change' after a successfull fetch", function() { + spyOn(this.view.model.interactions.likes, "trigger"); + this.view._showAll("likes", this.view.model.interactions.likes); + jasmine.Ajax.requests.mostRecent().respondWith({status: 200, responseText: "{\"id\": 1}"}); + expect(this.view.model.interactions.likes.trigger).toHaveBeenCalledWith("change"); + }); + }); + + context("with reshares", function() { + it("hides the #show-all-reshares link", function() { + expect(this.view.$("#show-all-likes")).not.toHaveClass("hidden"); + expect(this.view.$("#show-all-reshares")).not.toHaveClass("hidden"); + this.view._showAll("reshares", this.view.model.interactions.reshares); + expect(this.view.$("#show-all-likes")).not.toHaveClass("hidden"); + expect(this.view.$("#show-all-reshares")).toHaveClass("hidden"); + }); + + it("shows the reshares loader", function() { + expect(this.view.$("#likes .loader")).toHaveClass("hidden"); + expect(this.view.$("#reshares .loader")).toHaveClass("hidden"); + this.view._showAll("reshares", this.view.model.interactions.reshares); + expect(this.view.$("#likes .loader")).toHaveClass("hidden"); + expect(this.view.$("#reshares .loader")).not.toHaveClass("hidden"); + }); + + it("calls #fetch on the model", function() { + spyOn(this.view.model.interactions.reshares, "fetch"); + this.view._showAll("reshares", this.view.model.interactions.reshares); + expect(this.view.model.interactions.reshares.fetch).toHaveBeenCalled(); + }); + + it("triggers 'change' after a successfull fetch", function() { + spyOn(this.view.model.interactions.reshares, "trigger"); + this.view._showAll("reshares", this.view.model.interactions.reshares); + jasmine.Ajax.requests.mostRecent().respondWith({status: 200, responseText: "{\"id\": 1}"}); + expect(this.view.model.interactions.reshares.trigger).toHaveBeenCalledWith("change"); + }); + }); }); }); diff --git a/spec/javascripts/jasmine_helpers/factory.js b/spec/javascripts/jasmine_helpers/factory.js index 551fe87bc..ba97e5b71 100644 --- a/spec/javascripts/jasmine_helpers/factory.js +++ b/spec/javascripts/jasmine_helpers/factory.js @@ -21,6 +21,16 @@ var factory = { return _.extend(defaultAttrs, overrides); }, + reshare: function(overrides) { + var defaultAttrs = { + "created_at": "2012-01-04T00:55:30Z", + "author": this.author(), + "guid": this.guid(), + "id": this.id.next() + }; + return _.extend(defaultAttrs, overrides); + }, + aspectMembershipAttrs: function(overrides) { var id = this.id.next(); var defaultAttrs = { @@ -207,6 +217,24 @@ var factory = { return new app.models.Post(_.extend(defaultAttrs, overrides)); }, + postWithInteractions: function(overrides) { + var likes = _.range(10).map(function() { return factory.like(); }); + var reshares = _.range(15).map(function() { return factory.reshare(); }); + var comments = _.range(20).map(function() { return factory.comment(); }); + var defaultAttrs = _.extend(factory.postAttrs(), { + "author": this.author(), + "interactions": { + "reshares_count": 15, + "likes_count": 10, + "comments_count": 20, + "comments": comments, + "likes": likes, + "reshares": reshares + } + }); + return new app.models.Post(_.extend(defaultAttrs, overrides)); + }, + statusMessage : function(overrides){ //intentionally doesn't have an author to mirror creation process, maybe we should change the creation process return new app.models.StatusMessage(_.extend(factory.postAttrs(), overrides)); diff --git a/spec/presenters/post_presenter_spec.rb b/spec/presenters/post_presenter_spec.rb index 952da0a2c..4110cedeb 100644 --- a/spec/presenters/post_presenter_spec.rb +++ b/spec/presenters/post_presenter_spec.rb @@ -1,44 +1,93 @@ describe PostPresenter do - before do - @sm = FactoryGirl.create(:status_message, public: true) - @sm_with_poll = FactoryGirl.create(:status_message_with_poll, public: true) - @presenter = PostPresenter.new(@sm, bob) - @unauthenticated_presenter = PostPresenter.new(@sm) - end + let(:status_message) { FactoryGirl.create(:status_message, public: true) } + let(:status_message_with_poll) { FactoryGirl.create(:status_message_with_poll, public: true) } + let(:presenter) { PostPresenter.new(status_message, bob) } + let(:unauthenticated_presenter) { PostPresenter.new(status_message) } it "takes a post and an optional user" do - expect(@presenter).not_to be_nil + expect(presenter).not_to be_nil end describe "#as_json" do it "works with a user" do - expect(@presenter.as_json).to be_a Hash + expect(presenter.as_json).to be_a Hash end it "works without a user" do - expect(@unauthenticated_presenter.as_json).to be_a Hash + expect(unauthenticated_presenter.as_json).to be_a Hash + end + end + + context "post with interactions" do + before do + bob.like!(status_message) + bob.reshare!(status_message) + end + + describe "#with_interactions" do + it "works with a user" do + post_hash = presenter.with_interactions + expect(post_hash).to be_a Hash + expect(post_hash[:interactions]).to eq PostInteractionPresenter.new(status_message, bob).as_json + end + + it "works without a user" do + post_hash = unauthenticated_presenter.with_interactions + expect(post_hash).to be_a Hash + expect(post_hash[:interactions]).to eq PostInteractionPresenter.new(status_message, nil).as_json + end + end + + describe "#with_initial_interactions" do + it "works with a user" do + post_hash = presenter.with_initial_interactions + expect(post_hash).to be_a Hash + expect(post_hash[:interactions][:likes]).to eq( + LikeService.new(bob).find_for_post(status_message.id).as_api_response(:backbone) + ) + expect(post_hash[:interactions][:reshares]).to eq( + ReshareService.new(bob).find_for_post(status_message.id).as_api_response(:backbone) + ) + end + + it "works without a user" do + post_hash = unauthenticated_presenter.with_initial_interactions + expect(post_hash).to be_a Hash + expect(post_hash[:interactions][:likes]).to eq( + LikeService.new.find_for_post(status_message.id).as_api_response(:backbone) + ) + expect(post_hash[:interactions][:reshares]).to eq( + ReshareService.new.find_for_post(status_message.id).as_api_response(:backbone) + ) + end end end describe "#user_like" do + before do + bob.like!(status_message) + end + it "includes the users like" do - bob.like!(@sm) - expect(@presenter.send(:user_like)).to be_present + expect(presenter.send(:user_like)).to be_present end it "is nil if the user is not authenticated" do - expect(@unauthenticated_presenter.send(:user_like)).to be_nil + expect(unauthenticated_presenter.send(:user_like)).to be_nil end end describe "#user_reshare" do + before do + bob.reshare!(status_message) + end + it "includes the users reshare" do - bob.reshare!(@sm) - expect(@presenter.send(:user_reshare)).to be_present + expect(presenter.send(:user_reshare)).to be_present end it "is nil if the user is not authenticated" do - expect(@unauthenticated_presenter.send(:user_reshare)).to be_nil + expect(unauthenticated_presenter.send(:user_reshare)).to be_nil end end @@ -67,23 +116,23 @@ describe PostPresenter do it "delegates to message.title" do message = double(present?: true) expect(message).to receive(:title) - @presenter.post = double(message: message) - @presenter.send(:title) + presenter.post = double(message: message) + presenter.send(:title) end end context "with posts without text" do it "displays a messaage with the post class" do - @sm = double(message: double(present?: false), author: bob.person, author_name: bob.person.name) - @presenter.post = @sm - expect(@presenter.send(:title)).to eq("A post from #{@sm.author.name}") + sm = double(message: double(present?: false), author: bob.person, author_name: bob.person.name) + presenter.post = sm + expect(presenter.send(:title)).to eq("A post from #{sm.author.name}") end end end describe "#poll" do it "works without a user" do - presenter = PostPresenter.new(@sm_with_poll) + presenter = PostPresenter.new(status_message_with_poll) expect(presenter.as_json).to be_a(Hash) end end @@ -134,15 +183,15 @@ describe PostPresenter do describe "#build_open_graph_cache" do it "returns a dummy og cache if the og cache is missing" do - expect(@presenter.build_open_graph_cache.image).to be_nil + expect(presenter.build_open_graph_cache.image).to be_nil end context "with an open graph cache" do it "delegates to as_api_response" do og_cache = double("open_graph_cache") expect(og_cache).to receive(:as_api_response).with(:backbone) - @presenter.post = double(open_graph_cache: og_cache) - @presenter.send(:build_open_graph_cache) + presenter.post = double(open_graph_cache: og_cache) + presenter.send(:build_open_graph_cache) end it "returns the open graph cache data" do