diff --git a/Changelog.md b/Changelog.md index 9ad301b52..6ba90fd85 100644 --- a/Changelog.md +++ b/Changelog.md @@ -71,6 +71,7 @@ With the port to Bootstrap 3, app/views/terms/default.haml has a new structure. * Add support for relay based public post federation [#6207](https://github.com/diaspora/diaspora/pull/6207) * Bigger mobile publisher [#6261](https://github.com/diaspora/diaspora/pull/6261) * Backend information panel & health checks for known pods [#6290](https://github.com/diaspora/diaspora/pull/6290) +* Allow users to view a posts locations on an OpenStreetMap [#6256](https://github.com/diaspora/diaspora/pull/6256) # 0.5.4.0 diff --git a/Gemfile b/Gemfile index 316af33b0..65911040f 100644 --- a/Gemfile +++ b/Gemfile @@ -128,6 +128,9 @@ gem "rails-i18n", "4.0.5" gem "markerb", "1.1.0" gem "messagebus_ruby_api", "1.0.3" +# Map +gem "leaflet-rails", "0.7.4" + # Parsing gem "nokogiri", "1.6.6.2" diff --git a/Gemfile.lock b/Gemfile.lock index 07c08f053..862cdbe51 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -418,6 +418,7 @@ GEM actionpack (>= 3.0.0) activesupport (>= 3.0.0) kgio (2.10.0) + leaflet-rails (0.7.4) listen (3.0.3) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) @@ -841,6 +842,7 @@ DEPENDENCIES jshintrb (= 0.3.0) json (= 1.8.3) json-schema (= 2.5.1) + leaflet-rails (= 0.7.4) logging-rails (= 0.5.0) markerb (= 1.1.0) messagebus_ruby_api (= 1.0.3) diff --git a/app/assets/javascripts/app/views/content_view.js b/app/assets/javascripts/app/views/content_view.js index bd00d36a4..d462dd067 100644 --- a/app/assets/javascripts/app/views/content_view.js +++ b/app/assets/javascripts/app/views/content_view.js @@ -29,7 +29,6 @@ app.views.Content = app.views.Base.extend({ return photos; }, - expandPost: function(evt) { var el = $(this.el).find('.collapsible'); el.removeClass('collapsed').addClass('opened'); @@ -40,8 +39,8 @@ app.views.Content = app.views.Base.extend({ }, location: function(){ - var address = this.model.get('address')? this.model.get('address') : ''; - return address; + var location = this.model.get("location")? this.model.get("location") : ""; + return location; }, collapseOversized : function() { @@ -155,4 +154,5 @@ app.views.SPVOpenGraph = app.views.OpenGraph.extend({ // override with nothing } }); + // @license-end diff --git a/app/assets/javascripts/app/views/location_stream.js b/app/assets/javascripts/app/views/location_stream.js index fd6fae946..1db156dea 100644 --- a/app/assets/javascripts/app/views/location_stream.js +++ b/app/assets/javascripts/app/views/location_stream.js @@ -1,7 +1,54 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later app.views.LocationStream = app.views.Content.extend({ - templateName: "status-message-location" + events: { + "click .near-from": "toggleMap" + }, + templateName: "status-message-location", + + toggleMap: function () { + var mapContainer = this.$el.find(".mapContainer"); + + if (mapContainer.hasClass("empty")) { + var location = this.model.get("location"); + mapContainer.css("height", "150px"); + + if (location.lat) { + // If map function is enabled the maptiles from the Heidelberg University are used by default. + + var map = L.map(mapContainer[0]).setView([location.lat, location.lng], 14); + + var tiles = L.tileLayer("http://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}", { + attribution: "Map data © OpenStreetMap contributors, " + + "rendering " + + "GIScience Research Group @ Heidelberg University", + maxZoom: 18, + }); + + // If the mapbox option is enabled in the diaspora.yml, the mapbox tiles with the podmin's credentials are used. + if (gon.appConfig.map.mapbox.enabled) { + + tiles = L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", { + id: gon.appConfig.map.mapbox.id, + /* jshint camelcase: false */ + accessToken: gon.appConfig.map.mapbox.access_token, + /* jshint camelcase: true */ + attribution: "Map data © OpenStreetMap contributors, " + + "CC-BY-SA, " + + "Imagery © Mapbox", + maxZoom: 18, + }); + } + + tiles.addTo(map); + + L.marker(location).addTo(map); + mapContainer.removeClass("empty"); + return map; + } + } else { + mapContainer.toggle(); + } + } }); // @license-end - diff --git a/app/assets/javascripts/app/views/location_view.js b/app/assets/javascripts/app/views/locator.js similarity index 100% rename from app/assets/javascripts/app/views/location_view.js rename to app/assets/javascripts/app/views/locator.js diff --git a/app/assets/javascripts/app/views/single-post-viewer/single_post_content_view.js b/app/assets/javascripts/app/views/single-post-viewer/single_post_content_view.js index 57bbbb791..b105a74c4 100644 --- a/app/assets/javascripts/app/views/single-post-viewer/single_post_content_view.js +++ b/app/assets/javascripts/app/views/single-post-viewer/single_post_content_view.js @@ -1,6 +1,10 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later app.views.SinglePostContent = app.views.Base.extend({ + events: { + "click .near-from": "toggleMap" + }, + templateName: "single-post-viewer/single-post-content", tooltipSelector: "time, .post_scope", @@ -10,8 +14,7 @@ app.views.SinglePostContent = app.views.Base.extend({ "#real-post-content" : "postContentView", ".oembed" : "oEmbedView", ".opengraph" : "openGraphView", - ".status-message-location" : "postLocationStreamView", - ".poll": "pollView" + ".poll": "pollView", }, initialize : function() { @@ -23,8 +26,59 @@ app.views.SinglePostContent = app.views.Base.extend({ this.pollView = new app.views.Poll({ model: this.model }); }, - postLocationStreamView : function(){ - return new app.views.LocationStream({ model : this.model}); + map : function(){ + if (this.$el.find(".mapContainer")){ + + // find and set height of mapContainer to max size of the container + // which is necessary to have all necessary tiles prerendered + var mapContainer = this.$el.find(".mapContainer"); + mapContainer.css("height", "200px"); + + // get location data and render map + var location = this.model.get("location"); + + // If map function is enabled the maptiles from the Heidelberg University are used by default. + + var map = L.map(mapContainer[0]).setView([location.lat, location.lng], 14); + + var tiles = L.tileLayer("http://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}", { + attribution: "Map data © OpenStreetMap contributors, " + + "rendering " + + "GIScience Research Group @ Heidelberg University", + maxZoom: 18, + }); + + // If the mapbox option is enabled in the diaspora.yml, the mapbox tiles with the podmin's credentials are used. + if (gon.appConfig.map.mapbox.enabled) { + + tiles = L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", { + id: gon.appConfig.map.mapbox.id, + /* jshint camelcase: false */ + accessToken: gon.appConfig.map.mapbox.access_token, + /* jshint camelcase: true */ + attribution: "Map data © OpenStreetMap contributors, " + + "CC-BY-SA, " + + "Imagery © Mapbox", + maxZoom: 18, + }); + } + + tiles.addTo(map); + + // set mapContainer size to a smaller preview size + mapContainer.css("height", "75px"); + map.invalidateSize(); + + // put marker on map + L.marker(location).addTo(map); + return map; + } + }, + + toggleMap: function () { + $(".mapContainer").height($(".small-map")[0] ? 200 : 50); + $(".leaflet-control-zoom").css("display", $(".small-map")[0] ? "block" : "none"); + $(".mapContainer").toggleClass("small-map"); }, presenter : function() { @@ -37,6 +91,10 @@ app.views.SinglePostContent = app.views.Base.extend({ showPost : function() { return (app.currentUser.get("showNsfw")) || !this.model.get("nsfw"); + }, + + postRenderTemplate : function(){ + _.defer(_.bind(this.map, this)); } }); // @license-end diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 93891fb97..adc3bcc7a 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -45,3 +45,4 @@ //= require osmlocator //= require bootstrap-switch //= require blueimp-gallery +//= require leaflet diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index aafacb92d..38302959e 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -42,6 +42,10 @@ @import 'single-post-view'; @import 'new_styles/poll'; +/* map*/ +@import 'leaflet'; +@import 'map'; + /* conversations */ @import 'conversations'; diff --git a/app/assets/stylesheets/map.scss b/app/assets/stylesheets/map.scss new file mode 100644 index 000000000..f304d135b --- /dev/null +++ b/app/assets/stylesheets/map.scss @@ -0,0 +1,21 @@ +.mapContainer { + position: relative; + overflow: hidden; +} + +.near-from:hover { + cursor: pointer; + text-decoration: underline; +} + +.leaflet-control-zoom { + display: none; +} + +.leaflet-bottom .leaflet-control { + margin-bottom: 0; +} + +.leaflet-right .leaflet-control { + margin-right: 0; +} diff --git a/app/assets/stylesheets/single-post-view.scss b/app/assets/stylesheets/single-post-view.scss index 82bf4d5bc..0166d1802 100644 --- a/app/assets/stylesheets/single-post-view.scss +++ b/app/assets/stylesheets/single-post-view.scss @@ -23,6 +23,14 @@ padding-left: 10px; } } + .near-from { + color: $text-grey; + font-size: 12px; + margin: 10px 20px 0px 15px; + } + .mapContainer { + margin: 10px 20px 0px 15px; + } .row.reshare { border-top: 1px solid lighten($border-grey,5%); padding-top: 10px; diff --git a/app/assets/stylesheets/stream_element.scss b/app/assets/stylesheets/stream_element.scss index df46a5904..2449a3c47 100644 --- a/app/assets/stylesheets/stream_element.scss +++ b/app/assets/stylesheets/stream_element.scss @@ -83,10 +83,13 @@ float: left; margin-top: 6px; } - .status-message-location .near-from { + .status-message-location { font-size: $font-size-small; color: $text-grey; } + .leaflet-control-zoom { + display: block; + } .grey { color: $text-grey; } .post-content p:last-of-type { margin-bottom: 0; } .nsfw-shield { diff --git a/app/assets/templates/single-post-viewer/single-post-content_tpl.jst.hbs b/app/assets/templates/single-post-viewer/single-post-content_tpl.jst.hbs index 7c61fe741..9295117eb 100644 --- a/app/assets/templates/single-post-viewer/single-post-content_tpl.jst.hbs +++ b/app/assets/templates/single-post-viewer/single-post-content_tpl.jst.hbs @@ -57,7 +57,6 @@ {{t "stream.via" provider=provider_display_name}} {{/if}} {{/if}} -
{{#unless root}}
@@ -68,6 +67,14 @@
{{/unless}}
+ {{#if location.lat}} +
+
+ {{t "publisher.near_from" location=location.address}} +
+
+
+ {{/if}} {{#if root}}
diff --git a/app/assets/templates/status-message-location_tpl.jst.hbs b/app/assets/templates/status-message-location_tpl.jst.hbs index c6c7ea7ec..1cb8bda65 100644 --- a/app/assets/templates/status-message-location_tpl.jst.hbs +++ b/app/assets/templates/status-message-location_tpl.jst.hbs @@ -1,5 +1,8 @@ -{{#if location}} +{{#if location.address}}
- {{ t "publisher.near_from" location=location}} + {{t "publisher.near_from" location=location.address}} +
+
+
{{/if}} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index eb06e2f9e..19010ca93 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -145,7 +145,8 @@ class ApplicationController < ActionController::Base def gon_set_appconfig gon.push(appConfig: { chat: {enabled: AppConfig.chat.enabled?}, - settings: {podname: AppConfig.settings.pod_name} + settings: {podname: AppConfig.settings.pod_name}, + map: {mapbox: AppConfig.map.mapbox} }) end diff --git a/app/models/reshare.rb b/app/models/reshare.rb index 93803af1d..456d12866 100644 --- a/app/models/reshare.rb +++ b/app/models/reshare.rb @@ -3,7 +3,6 @@ # the COPYRIGHT file. class Reshare < Post - belongs_to :root, :class_name => 'Post', :foreign_key => :root_guid, :primary_key => :guid validate :root_must_be_public validates_presence_of :root, :on => :create @@ -45,8 +44,12 @@ class Reshare < Post absolute_root.try(:photos) || super end - def address - absolute_root.try(:location).try(:address) + def post_location + { + address: absolute_root.try(:location).try(:address), + lat: absolute_root.try(:location).try(:lat), + lng: absolute_root.try(:location).try(:lng) + } end def poll diff --git a/app/models/status_message.rb b/app/models/status_message.rb index 4dacee42c..3e6605866 100644 --- a/app/models/status_message.rb +++ b/app/models/status_message.rb @@ -158,8 +158,12 @@ class StatusMessage < Post self.open_graph_url = self.message.urls[0] end - def address - location.try(:address) + def post_location + { + address: location.try(:address), + lat: location.try(:lat), + lng: location.try(:lng) + } end protected diff --git a/app/presenters/post_presenter.rb b/app/presenters/post_presenter.rb index b1df1a917..8e95647f4 100644 --- a/app/presenters/post_presenter.rb +++ b/app/presenters/post_presenter.rb @@ -31,7 +31,7 @@ class PostPresenter < BasePresenter photos: build_photos_json, root: root, title: title, - address: @post.address, + location: @post.post_location, poll: @post.poll, already_participated_in_poll: already_participated_in_poll, participation: participate?, diff --git a/config/.jshint.json b/config/.jshint.json index 15675a257..cf93d1e6e 100644 --- a/config/.jshint.json +++ b/config/.jshint.json @@ -46,6 +46,7 @@ "HandlebarsTemplates", "ImagePaths", "jsxc", + "L", "MBP", "Routes", "OSM", diff --git a/config/defaults.yml b/config/defaults.yml index c607dcdd0..d80e51e38 100644 --- a/config/defaults.yml +++ b/config/defaults.yml @@ -76,6 +76,11 @@ defaults: log: file: 'log/vines.log' level: 'info' + map: + mapbox: + enabled: false + id: + access_token: privacy: jquery_cdn: false google_analytics_key: diff --git a/config/diaspora.yml.example b/config/diaspora.yml.example index 2e32b45fe..c38cebec0 100644 --- a/config/diaspora.yml.example +++ b/config/diaspora.yml.example @@ -327,6 +327,20 @@ configuration: ## Section ## The debug level logs all XML sent and received by the server. #level: 'info' + ## Displays the location of a post in a map. Per default we are using the map + ## tiles of the Heidelberg University (http://giscience.uni-hd.de). + ## You also have the possibility to use the map tiles of https://www.mapbox.com + ## which is probably more reliable. There you have to create an account to get + ## an ID and an access token which is limited. If you want to get an unlimited + ## account you can write an email to team@diasporafoundation.org. + ## Please enable mapbox and fill out your id and access_token. + map: ##Section + + mapbox: + # enabled: false + # id: 'your.id' + # access_token: 'youraccesstoken' + ## Settings potentially affecting the privacy of your users. privacy: ## Section diff --git a/spec/factories.rb b/spec/factories.rb index 1f99c0f0b..056bb1928 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -140,9 +140,9 @@ FactoryGirl.define do end factory(:location) do - address "unicorn city" - lat 1 - lng 2 + address "Fernsehturm Berlin, Berlin, Germany" + lat 52.520645 + lng 13.409779 end factory(:poll) do diff --git a/spec/javascripts/app/views/content_view_spec.js b/spec/javascripts/app/views/content_view_spec.js index 6a701aaa3..90fa3c4af 100644 --- a/spec/javascripts/app/views/content_view_spec.js +++ b/spec/javascripts/app/views/content_view_spec.js @@ -24,5 +24,10 @@ describe("app.views.Content", function(){ this.post.set({post_type : "Reshare"}); expect(this.view.presenter().isReshare).toBeTruthy(); }); + + it("provides location", function(){ + this.post.set({location : factory.location()}); + expect(this.view.presenter().location).toEqual(factory.location()); + }); }); }); diff --git a/spec/javascripts/app/views/location_stream_spec.js b/spec/javascripts/app/views/location_stream_spec.js new file mode 100644 index 000000000..ba0d2e2db --- /dev/null +++ b/spec/javascripts/app/views/location_stream_spec.js @@ -0,0 +1,45 @@ +describe("app.views.LocationStream", function() { + beforeEach(function(){ + this.post = factory.post(); + this.view = new app.views.LocationStream({model : this.post}); + /* jshint camelcase: false */ + gon.appConfig = {map: { mapbox: {enabled: true, id: "yourID", access_token: "yourAccessToken" }}}; + /* jshint camelcase: true */ + }); + + describe("toggleMap", function() { + context("with location provided", function() { + beforeEach(function(){ + this.post.set({location : factory.location()}); // set location + spec.content().html(this.view.render().el); // loads html element to the page + }); + + it("should contain a map container", function() { + expect(spec.content()).toContainElement(".mapContainer"); + }); + + it("should initialize map", function() { + expect($(".mapContainer")).toHaveClass("empty"); + this.view.toggleMap(); + expect($(".mapContainer")).not.toHaveClass("empty"); + }); + + it("should change display status on every click", function() { + this.view.toggleMap(); + expect($(".mapContainer")).toHaveCss({display: "block"}); + this.view.toggleMap(); + expect($(".mapContainer")).toHaveCss({display: "none"}); + }); + }); + + context("without location provided", function() { + beforeEach(function(){ + spec.content().html(this.view.render().el); + }); + + it("should not initialize the map", function() { + expect(spec.content()).not.toContainElement(".mapContainer"); + }); + }); + }); +}); diff --git a/spec/javascripts/app/views/location_view_spec.js b/spec/javascripts/app/views/locator_spec.js similarity index 96% rename from spec/javascripts/app/views/location_view_spec.js rename to spec/javascripts/app/views/locator_spec.js index 42e04741c..f600de8b9 100644 --- a/spec/javascripts/app/views/location_view_spec.js +++ b/spec/javascripts/app/views/locator_spec.js @@ -1,6 +1,5 @@ describe("app.views.Location", function(){ beforeEach(function(){ - OSM = {}; OSM.Locator = function(){return { getAddress:function(){}}}; this.view = new app.views.Location(); diff --git a/spec/javascripts/app/views/single_post_content_view_spec.js b/spec/javascripts/app/views/single_post_content_view_spec.js new file mode 100644 index 000000000..645e86a31 --- /dev/null +++ b/spec/javascripts/app/views/single_post_content_view_spec.js @@ -0,0 +1,50 @@ +describe("app.views.SinglePostContent", function() { + beforeEach(function(){ + this.post = factory.post(); + this.view = new app.views.SinglePostContent({model : this.post}); + gon.appConfig = { map: {mapbox: {enabled: true, id: "yourID", accessToken: "yourAccessToken" }}}; + }); + + describe("toggleMap", function() { + context("with location provided", function() { + beforeEach(function(){ + this.post.set({location : factory.location()}); // set location + spec.content().html(this.view.render().el); // loads html element to the page + }); + + it("should contain a map container", function() { + expect(spec.content()).toContainElement(".mapContainer"); + }); + + it("should provide a small map", function() { + expect($(".mapContainer")).toHaveClass("small-map"); + expect($(".mapContainer").height() < 100).toBeTruthy(); + expect($(".mapContainer")).toBeVisible(); + }); + + it("should toggle class small-map on every click", function(){ + this.view.toggleMap(); + expect($(".mapContainer")).not.toHaveClass("small-map"); + this.view.toggleMap(); + expect($(".mapContainer")).toHaveClass("small-map"); + }); + + it("should change height on every click", function() { + this.view.toggleMap(); + expect($(".mapContainer").height() > 100).toBeTruthy(); + this.view.toggleMap(); + expect($(".mapContainer").height() < 100).toBeTruthy(); + }); + }); + + context("without location provided", function() { + beforeEach(function(){ + spec.content().html(this.view.render().el); + }); + + it("should not initialize the map", function() { + expect(spec.content()).not.toContainElement(".mapContainer"); + }); + }); + }); +}); diff --git a/spec/javascripts/helpers/factory.js b/spec/javascripts/helpers/factory.js index 40aeaf39f..856e8301a 100644 --- a/spec/javascripts/helpers/factory.js +++ b/spec/javascripts/helpers/factory.js @@ -148,6 +148,14 @@ var factory = { }, overrides); }, + location : function() { + return { + address: "Starco Mart, Mission Street, San Francisco, Kalifornien, 94103, Vereinigte Staaten von Amerika", + lat: 37.78, + lng: -122.41 + }; + }, + post : function(overrides) { var defaultAttrs = _.extend(factory.postAttrs(), {"author" : this.author()}); return new app.models.Post(_.extend(defaultAttrs, overrides)); diff --git a/spec/models/reshare_spec.rb b/spec/models/reshare_spec.rb index e235c4066..0e13aacd4 100644 --- a/spec/models/reshare_spec.rb +++ b/spec/models/reshare_spec.rb @@ -1,33 +1,33 @@ -require 'spec_helper' +require "spec_helper" -describe Reshare, :type => :model do - it 'has a valid Factory' do +describe Reshare, type: :model do + it "has a valid Factory" do expect(FactoryGirl.build(:reshare)).to be_valid end - it 'requires root' do - reshare = FactoryGirl.build(:reshare, :root => nil) + it "requires root" do + reshare = FactoryGirl.build(:reshare, root: nil) expect(reshare).not_to be_valid end - it 'require public root' do - reshare = FactoryGirl.build(:reshare, :root => FactoryGirl.create(:status_message, :public => false)) + it "require public root" do + reshare = FactoryGirl.build(:reshare, root: FactoryGirl.create(:status_message, public: false)) expect(reshare).not_to be_valid - expect(reshare.errors[:base]).to include('Only posts which are public may be reshared.') + expect(reshare.errors[:base]).to include("Only posts which are public may be reshared.") end - it 'forces public' do - expect(FactoryGirl.create(:reshare, :public => false).public).to be true + it "forces public" do + expect(FactoryGirl.create(:reshare, public: false).public).to be true end describe "#root_diaspora_id" do + let(:reshare) { create(:reshare, root: FactoryGirl.build(:status_message, author: bob.person, public: true)) } + it "should return the root diaspora id" do - reshare = FactoryGirl.create(:reshare, root: FactoryGirl.build(:status_message, author: bob.person, public: true)) expect(reshare.root_diaspora_id).to eq(bob.person.diaspora_handle) end it "should be nil if no root found" do - reshare = FactoryGirl.create(:reshare, root: FactoryGirl.build(:status_message, author: bob.person, public: true)) reshare.root = nil expect(reshare.root_diaspora_id).to be_nil end @@ -37,195 +37,188 @@ describe Reshare, :type => :model do let(:receive_reshare) { @reshare.receive(@root.author.owner, @reshare.author) } before do - @reshare = FactoryGirl.create(:reshare, :root => FactoryGirl.build(:status_message, :author => bob.person, :public => true)) + @reshare = FactoryGirl.create(:reshare, root: + FactoryGirl.build(:status_message, author: bob.person, public: true)) @root = @reshare.root end - it 'increments the reshare count' do + it "increments the reshare count" do receive_reshare expect(@root.resharers.count).to eq(1) end - it 'adds the resharer to the re-sharers of the post' do + it "adds the resharer to the re-sharers of the post" do receive_reshare expect(@root.resharers).to include(@reshare.author) end - it 'does not error if the root author has a contact for the resharer' do + it "does not error if the root author has a contact for the resharer" do bob.share_with @reshare.author, bob.aspects.first expect { Timeout.timeout(5) do - receive_reshare #This doesn't ever terminate on my machine before it was fixed. + receive_reshare # This doesn't ever terminate on my machine before it was fixed. end }.not_to raise_error end end - describe '#nsfw' do - before do - sfw = FactoryGirl.build(:status_message, :author => alice.person, :public => true) - nsfw = FactoryGirl.build(:status_message, :author => alice.person, :public => true, :text => "This is #nsfw") - @sfw_reshare = FactoryGirl.build(:reshare, :root => sfw) - @nsfw_reshare = FactoryGirl.build(:reshare, :root => nsfw) - end + describe "#nsfw" do + let(:sfw) { build(:status_message, author: alice.person, public: true) } + let(:nsfw) { build(:status_message, author: alice.person, public: true, text: "This is #nsfw") } + let(:sfw_reshare) { build(:reshare, root: sfw) } + let(:nsfw_reshare) { build(:reshare, root: nsfw) } - it 'deletates #nsfw to the root post' do - expect(@sfw_reshare.nsfw).not_to be true - expect(@nsfw_reshare.nsfw).to be_truthy + it "deletates #nsfw to the root post" do + expect(sfw_reshare.nsfw).not_to be true + expect(nsfw_reshare.nsfw).to be_truthy end end - describe '#poll' do - before do - @root_post = FactoryGirl.create(:status_message_with_poll, public: true) - @reshare = FactoryGirl.create(:reshare, root: @root_post) - end + describe "#poll" do + let(:root_post) { create(:status_message_with_poll, public: true) } + let(:reshare) { create(:reshare, root: root_post) } - it 'contains root poll' do - expect(@reshare.poll).to eq @root_post.poll + it "contains root poll" do + expect(reshare.poll).to eq root_post.poll end end - describe '#notification_type' do - before do - sm = FactoryGirl.build(:status_message, :author => alice.person, :public => true) - @reshare = FactoryGirl.build(:reshare, :root => sm) - end - it 'does not return anything for non-author of the original post' do - expect(@reshare.notification_type(bob, @reshare.author)).to be_nil + describe "#notification_type" do + let(:status_message) { build(:status_message, author: alice.person, public: true) } + let(:reshare) { build(:reshare, root: status_message) } + + it "does not return anything for non-author of the original post" do + expect(reshare.notification_type(bob, reshare.author)).to be_nil end - it 'returns "Reshared" for the original post author' do - expect(@reshare.notification_type(alice, @reshare.author)).to eq(Notifications::Reshared) + it "returns 'Reshared' for the original post author" do + expect(reshare.notification_type(alice, reshare.author)).to eq(Notifications::Reshared) end - it 'does not error out if the root was deleted' do - @reshare.root = nil + it "does not error out if the root was deleted" do + reshare.root = nil expect { - @reshare.notification_type(alice, @reshare.author) + reshare.notification_type(alice, reshare.author) }.to_not raise_error end end - describe '#absolute_root' do + describe "#absolute_root" do before do - @sm = FactoryGirl.build(:status_message, :author => alice.person, :public => true) - rs1 = FactoryGirl.build(:reshare, :root=>@sm) - rs2 = FactoryGirl.build(:reshare, :root=>rs1) - @rs3 = FactoryGirl.build(:reshare, :root=>rs2) + @status_message = FactoryGirl.build(:status_message, author: alice.person, public: true) + reshare_1 = FactoryGirl.build(:reshare, root: @status_message) + reshare_2 = FactoryGirl.build(:reshare, root: reshare_1) + @reshare_3 = FactoryGirl.build(:reshare, root: reshare_2) - sm = FactoryGirl.create(:status_message, :author => alice.person, :public => true) - rs1 = FactoryGirl.create(:reshare, :root => sm) - @of_deleted = FactoryGirl.build(:reshare, :root => rs1) - sm.destroy - rs1.reload + status_message = FactoryGirl.create(:status_message, author: alice.person, public: true) + reshare_1 = FactoryGirl.create(:reshare, root: status_message) + @of_deleted = FactoryGirl.build(:reshare, root: reshare_1) + status_message.destroy + reshare_1.reload end - it 'resolves root posts to the top level' do - expect(@rs3.absolute_root).to eq(@sm) + it "resolves root posts to the top level" do + expect(@reshare_3.absolute_root).to eq(@status_message) end - it 'can handle deleted reshares' do + it "can handle deleted reshares" do expect(@of_deleted.absolute_root).to be_nil end - it 'is used everywhere' do - expect(@rs3.message).to eq @sm.message + it "is used everywhere" do + expect(@reshare_3.message).to eq @status_message.message expect(@of_deleted.message).to be_nil - expect(@rs3.photos).to eq @sm.photos + expect(@reshare_3.photos).to eq @status_message.photos expect(@of_deleted.photos).to be_empty - expect(@rs3.o_embed_cache).to eq @sm.o_embed_cache + expect(@reshare_3.o_embed_cache).to eq @status_message.o_embed_cache expect(@of_deleted.o_embed_cache).to be_nil - expect(@rs3.open_graph_cache).to eq @sm.open_graph_cache + expect(@reshare_3.open_graph_cache).to eq @status_message.open_graph_cache expect(@of_deleted.open_graph_cache).to be_nil - expect(@rs3.mentioned_people).to eq @sm.mentioned_people + expect(@reshare_3.mentioned_people).to eq @status_message.mentioned_people expect(@of_deleted.mentioned_people).to be_empty - expect(@rs3.nsfw).to eq @sm.nsfw + expect(@reshare_3.nsfw).to eq @status_message.nsfw expect(@of_deleted.nsfw).to be_nil - expect(@rs3.address).to eq @sm.location.try(:address) + expect(@reshare_3.address).to eq @status_message.location.try(:address) expect(@of_deleted.address).to be_nil end end describe "XML" do - before do - @reshare = FactoryGirl.build(:reshare) - @xml = @reshare.to_xml.to_s - end + let(:reshare) { build(:reshare) } + let(:xml) { reshare.to_xml.to_s } - context 'serialization' do - it 'serializes root_diaspora_id' do - expect(@xml).to include("root_diaspora_id") - expect(@xml).to include(@reshare.author.diaspora_handle) + context "serialization" do + it "serializes root_diaspora_id" do + expect(xml).to include("root_diaspora_id") + expect(xml).to include(reshare.author.diaspora_handle) end - it 'serializes root_guid' do - expect(@xml).to include("root_guid") - expect(@xml).to include(@reshare.root.guid) + it "serializes root_guid" do + expect(xml).to include("root_guid") + expect(xml).to include(reshare.root.guid) end end - context 'marshalling' do - context 'local' do - before do - @original_author = @reshare.root.author - @root_object = @reshare.root + context "marshalling" do + let(:root_object) { reshare.root } + + context "local" do + let(:original_author) { reshare.root.author } + + it "marshals the guid" do + expect(Reshare.from_xml(xml).root_guid).to eq(root_object.guid) end - it 'marshals the guid' do - expect(Reshare.from_xml(@xml).root_guid).to eq(@root_object.guid) + it "fetches the root post from root_guid" do + expect(Reshare.from_xml(xml).root).to eq(root_object) end - it 'fetches the root post from root_guid' do - expect(Reshare.from_xml(@xml).root).to eq(@root_object) - end - - it 'fetches the root author from root_diaspora_id' do - expect(Reshare.from_xml(@xml).root.author).to eq(@original_author) + it "fetches the root author from root_diaspora_id" do + expect(Reshare.from_xml(xml).root.author).to eq(original_author) end end - describe 'destroy' do - it 'allows you to destroy the reshare if the root post is missing' do - reshare = FactoryGirl.build(:reshare) + describe "destroy" do + it "allows you to destroy the reshare if the root post is missing" do + reshare reshare.root = nil - expect{ + expect { reshare.destroy }.to_not raise_error end end - context 'remote' do + context "remote" do before do - @root_object = @reshare.root - @root_object.delete + # root_object = reshare.root + root_object.delete @response = double allow(@response).to receive(:status).and_return(200) allow(@response).to receive(:success?).and_return(true) end - it 'fetches the root author from root_diaspora_id' do - @original_profile = @reshare.root.author.profile.dup - @reshare.root.author.profile.delete - @original_author = @reshare.root.author.dup - @reshare.root.author.delete + it "fetches the root author from root_diaspora_id" do + @original_profile = reshare.root.author.profile.dup + reshare.root.author.profile.delete + @original_author = reshare.root.author.dup + reshare.root.author.delete @original_author.profile = @original_profile expect(Person).to receive(:find_or_fetch_by_identifier).and_return(@original_author) - allow(@response).to receive(:body).and_return(@root_object.to_diaspora_xml) + allow(@response).to receive(:body).and_return(root_object.to_diaspora_xml) expect(Faraday.default_connection).to receive(:get).with( URI.join( @original_author.url, Rails.application.routes.url_helpers.short_post_path( - @root_object.guid, + root_object.guid, format: "xml" ) ) ).and_return(@response) - Reshare.from_xml(@xml) + Reshare.from_xml(xml) end context "fetching post" do @@ -234,7 +227,7 @@ describe Reshare, :type => :model do expect(Faraday.default_connection).to receive(:get).and_return(@response) expect { - Reshare.from_xml(@xml) + Reshare.from_xml(xml) }.to raise_error(Diaspora::PostNotFetchable) end @@ -244,57 +237,79 @@ describe Reshare, :type => :model do expect(Faraday.default_connection).to receive(:get).and_return(@response) expect { - Reshare.from_xml(@xml) + Reshare.from_xml(xml) }.to raise_error RuntimeError end end - context 'saving the post' do + context "saving the post" do before do - allow(@response).to receive(:body).and_return(@root_object.to_diaspora_xml) + allow(@response).to receive(:body).and_return(root_object.to_diaspora_xml) allow(Faraday.default_connection).to receive(:get).with( URI.join( - @reshare.root.author.url, + reshare.root.author.url, Rails.application.routes.url_helpers.short_post_path( - @root_object.guid, + root_object.guid, format: "xml" ) ) ).and_return(@response) end - it 'fetches the root post from root_guid' do - root = Reshare.from_xml(@xml).root + it "fetches the root post from root_guid" do + root = Reshare.from_xml(xml).root - [:text, :guid, :diaspora_handle, :type, :public].each do |attr| - expect(root.send(attr)).to eq(@reshare.root.send(attr)) + %i(text guid diaspora_handle type public).each do |attr| + expect(root.send(attr)).to eq(reshare.root.send(attr)) end end - it 'correctly saves the type' do - expect(Reshare.from_xml(@xml).root.reload.type).to eq("StatusMessage") + it "correctly saves the type" do + expect(Reshare.from_xml(xml).root.reload.type).to eq("StatusMessage") end - it 'correctly sets the author' do - @original_author = @reshare.root.author - expect(Reshare.from_xml(@xml).root.reload.author.reload).to eq(@original_author) + it "correctly sets the author" do + @original_author = reshare.root.author + expect(Reshare.from_xml(xml).root.reload.author.reload).to eq(@original_author) end - it 'verifies that the author of the post received is the same as the author in the reshare xml' do - @original_author = @reshare.root.author.dup - @xml = @reshare.to_xml.to_s + it "verifies that the author of the post received is the same as the author in the reshare xml" do + @original_author = reshare.root.author.dup + xml = reshare.to_xml.to_s different_person = FactoryGirl.build(:person) expect(Person).to receive(:find_or_fetch_by_identifier).and_return(different_person) allow(different_person).to receive(:url).and_return(@original_author.url) - expect{ - Reshare.from_xml(@xml) + expect { + Reshare.from_xml(xml) }.to raise_error /^Diaspora ID \(.+\) in the root does not match the Diaspora ID \(.+\) specified in the reshare!$/ end end end end end + + describe "#post_location" do + let(:status_message) { build(:status_message, text: "This is a status_message", author: bob.person, public: true) } + let(:reshare) { create(:reshare, root: status_message) } + + context "with location" do + let(:location) { build(:location) } + + it "should deliver address and coordinates" do + status_message.location = location + expect(reshare.post_location).to include(address: location.address, lat: location.lat, lng: location.lng) + end + end + + context "without location" do + it "should deliver empty address and coordinates" do + expect(reshare.post_location[:address]).to be_nil + expect(reshare.post_location[:lat]).to be_nil + expect(reshare.post_location[:lng]).to be_nil + end + end + end end diff --git a/spec/models/status_message_spec.rb b/spec/models/status_message_spec.rb index 0f37390a4..1bdafec56 100644 --- a/spec/models/status_message_spec.rb +++ b/spec/models/status_message_spec.rb @@ -2,56 +2,54 @@ # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. -require 'spec_helper' +require "spec_helper" -describe StatusMessage, :type => :model do +describe StatusMessage, type: :model do include PeopleHelper - before do - @user = alice - @aspect = @user.aspects.first - end + let!(:user) { alice } + let!(:aspect) { user.aspects.first } + let(:status) { build(:status_message) } - describe 'scopes' do - describe '.where_person_is_mentioned' do - it 'returns status messages where the given person is mentioned' do - @bo = bob.person - @test_string = "@{Daniel; #{@bo.diaspora_handle}} can mention people like Raph" + describe "scopes" do + describe ".where_person_is_mentioned" do + it "returns status messages where the given person is mentioned" do + @bob = bob.person + @test_string = "@{Daniel; #{@bob.diaspora_handle}} can mention people like Raph" + FactoryGirl.create(:status_message, text: @test_string) + FactoryGirl.create(:status_message, text: @test_string) + FactoryGirl.create(:status_message) - FactoryGirl.create(:status_message, :text => @test_string ) - FactoryGirl.create(:status_message, :text => @test_string ) - FactoryGirl.create(:status_message) - - expect(StatusMessage.where_person_is_mentioned(@bo).count).to eq(2) + expect(StatusMessage.where_person_is_mentioned(bob).count).to eq(2) end end context "tag_streams" do before do - @sm1 = FactoryGirl.create(:status_message, :text => "#hashtag" , :public => true) - @sm2 = FactoryGirl.create(:status_message, :text => "#hashtag" ) - @sm3 = FactoryGirl.create(:status_message, :text => "hashtags are #awesome", :public => true ) - @sm4 = FactoryGirl.create(:status_message, :text => "hashtags are #awesome" ) + @status_message_1 = FactoryGirl.create(:status_message, text: "#hashtag", public: true) + @status_message_2 = FactoryGirl.create(:status_message, text: "#hashtag") + @status_message_3 = FactoryGirl.create(:status_message, text: "hashtags are #awesome", public: true) + @status_message_4 = FactoryGirl.create(:status_message, text: "hashtags are #awesome") - @tag_id = ActsAsTaggableOn::Tag.where(:name => "hashtag").first.id + @tag_id = ActsAsTaggableOn::Tag.where(name: "hashtag").first.id end - describe '.tag_steam' do - it 'returns status messages tagged with the tag' do + describe ".tag_steam" do + it "returns status messages tagged with the tag" do tag_stream = StatusMessage.send(:tag_stream, [@tag_id]) - expect(tag_stream).to include @sm1 - expect(tag_stream).to include @sm2 + expect(tag_stream).to include @status_message_1 + expect(tag_stream).to include @status_message_2 end end - describe '.public_tag_stream' do - it 'returns public status messages tagged with the tag' do - expect(StatusMessage.public_tag_stream([@tag_id])).to eq([@sm1]) + describe ".public_tag_stream" do + it "returns public status messages tagged with the tag" do + expect(StatusMessage.public_tag_stream([@tag_id])).to eq([@status_message_1]) end end - describe '.user_tag_stream' do - it 'returns tag stream thats owned or visible by' do + describe ".user_tag_stream" do + it "returns tag stream thats owned or visible by" do relation = double expect(StatusMessage).to receive(:owned_or_visible_by_user).with(bob).and_return(relation) expect(relation).to receive(:tag_stream).with([@tag_id]) @@ -63,48 +61,45 @@ describe StatusMessage, :type => :model do end describe ".guids_for_author" do - it 'returns an array of the status_message guids' do - sm1 = FactoryGirl.create(:status_message, :author => alice.person) - sm2 = FactoryGirl.create(:status_message, :author => bob.person) + it "returns an array of the status_message guids" do + status_message_1 = FactoryGirl.create(:status_message, author: alice.person) + FactoryGirl.create(:status_message, author: bob.person) guids = StatusMessage.guids_for_author(alice.person) - expect(guids).to eq([sm1.guid]) + expect(guids).to eq([status_message_1.guid]) end end - describe '.before_validation' do - it 'calls build_tags' do - status = FactoryGirl.build(:status_message) + describe ".before_validation" do + it "calls build_tags" do expect(status).to receive(:build_tags) status.save end end - describe '.before_create' do - it 'calls build_tags' do - status = FactoryGirl.build(:status_message) + describe ".before_create" do + it "calls build_tags" do expect(status).to receive(:build_tags) status.save end - it 'calls filter_mentions' do - status = FactoryGirl.build(:status_message) + it "calls filter_mentions" do expect(status).to receive(:filter_mentions) status.save end end - describe '.after_create' do - it 'calls create_mentions' do + describe ".after_create" do + it "calls create_mentions" do status = FactoryGirl.build(:status_message, text: "text @{Test; #{alice.diaspora_handle}}") expect(status).to receive(:create_mentions).and_call_original status.save end end - describe '#diaspora_handle=' do - it 'sets #author' do + describe "#diaspora_handle=" do + it "sets #author" do person = FactoryGirl.create(:person) - post = FactoryGirl.build(:status_message, :author => @user.person) + post = FactoryGirl.build(:status_message, author: user.person) post.diaspora_handle = person.diaspora_handle expect(post.author).to eq(person) end @@ -112,105 +107,104 @@ describe StatusMessage, :type => :model do context "emptyness" do it "needs either a message or at least one photo" do - n = @user.build_post(:status_message, :text => nil) - expect(n).not_to be_valid + post = user.build_post(:status_message, text: nil) + expect(post).not_to be_valid - n.text = "" - expect(n).not_to be_valid + post.text = "" + expect(post).not_to be_valid - n.text = "wales" - expect(n).to be_valid - n.text = nil + post.text = "wales" + expect(post).to be_valid + post.text = nil - photo = @user.build_post(:photo, :user_file => uploaded_photo, :to => @aspect.id) + photo = user.build_post(:photo, user_file: uploaded_photo, to: aspect.id) photo.save! - n.photos << photo - expect(n).to be_valid - expect(n.errors.full_messages).to eq([]) + post.photos << photo + expect(post).to be_valid + expect(post.errors.full_messages).to eq([]) end it "doesn't check for content when author is remote (federation...)" do - p = FactoryGirl.build(:status_message, text: nil) - expect(p).to be_valid + post = FactoryGirl.build(:status_message, text: nil) + expect(post).to be_valid end end - it 'should be postable through the user' do + it "should be postable through the user" do message = "Users do things" - status = @user.post(:status_message, :text => message, :to => @aspect.id) + status = user.post(:status_message, text: message, to: aspect.id) db_status = StatusMessage.find(status.id) expect(db_status.text).to eq(message) end - it 'should require status messages not be more than 65535 characters long' do - message = 'a' * (65535+1) - status_message = FactoryGirl.build(:status_message, :text => message) + it "should require status messages not be more than 65535 characters long" do + message = "a" * (65_535 + 1) + status_message = FactoryGirl.build(:status_message, text: message) expect(status_message).not_to be_valid end - describe 'mentions' do - before do - @people = [alice, bob, eve].map{|u| u.person} - @test_string = <<-STR -@{Raphael; #{@people[0].diaspora_handle}} can mention people like Raphael @{Ilya; #{@people[1].diaspora_handle}} -can mention people like Raphaellike Raphael @{Daniel; #{@people[2].diaspora_handle}} can mention people like Raph -STR - @sm = FactoryGirl.create(:status_message, :text => @test_string ) - end + describe "mentions" do + let(:people) { [alice, bob, eve].map(&:person) } + let(:test_string) { + "@{Raphael; #{people[0].diaspora_handle}} can mention people like Raphael @{Ilya; #{people[1].diaspora_handle}} + can mention people like Raphaellike Raphael @{Daniel; #{people[2].diaspora_handle}} can mention people like Raph" + } + let(:status_message) { create(:status_message, text: test_string) } - describe '#create_mentions' do - it 'creates a mention for everyone mentioned in the message' do - expect(Diaspora::Mentionable).to receive(:people_from_string).and_return(@people) - @sm.mentions.delete_all - @sm.create_mentions - expect(@sm.mentions(true).map{|m| m.person}.to_set).to eq(@people.to_set) + describe "#create_mentions" do + it "creates a mention for everyone mentioned in the message" do + status_message + expect(Diaspora::Mentionable).to receive(:people_from_string).and_return(people) + status_message.mentions.delete_all + status_message.create_mentions + expect(status_message.mentions(true).map(&:person).to_set).to eq(people.to_set) end - it 'does not barf if it gets called twice' do - @sm.create_mentions + it "does not barf if it gets called twice" do + status_message.create_mentions - expect{ - @sm.create_mentions + expect { + status_message.create_mentions }.to_not raise_error end end - describe '#mentioned_people' do - it 'calls create_mentions if there are no mentions in the db' do - @sm.mentions.delete_all - expect(@sm).to receive(:create_mentions) - @sm.mentioned_people + describe "#mentioned_people" do + it "calls create_mentions if there are no mentions in the db" do + status_message.mentions.delete_all + expect(status_message).to receive(:create_mentions) + status_message.mentioned_people end - it 'returns the mentioned people' do - @sm.mentions.delete_all - expect(@sm.mentioned_people.to_set).to eq(@people.to_set) + it "returns the mentioned people" do + status_message.mentions.delete_all + expect(status_message.mentioned_people.to_set).to eq(people.to_set) end - it 'does not call create_mentions if there are mentions in the db' do - expect(@sm).not_to receive(:create_mentions) - @sm.mentioned_people + it "does not call create_mentions if there are mentions in the db" do + expect(status_message).not_to receive(:create_mentions) + status_message.mentioned_people end end describe "#mentions?" do - it 'returns true if the person was mentioned' do - expect(@sm.mentions?(@people[0])).to be true + it "returns true if the person was mentioned" do + expect(status_message.mentions?(people[0])).to be true end - it 'returns false if the person was not mentioned' do - expect(@sm.mentions?(FactoryGirl.build(:person))).to be false + it "returns false if the person was not mentioned" do + expect(status_message.mentions?(FactoryGirl.build(:person))).to be false end end describe "#notify_person" do - it 'notifies the person mentioned' do + it "notifies the person mentioned" do expect(Notification).to receive(:notify).with(alice, anything, anything) - @sm.notify_person(alice.person) + status_message.notify_person(alice.person) end end describe "#filter_mentions" do - it 'calls Diaspora::Mentionable#filter_for_aspects' do + it "calls Diaspora::Mentionable#filter_for_aspects" do msg = FactoryGirl.build(:status_message_in_aspect) msg_txt = msg.raw_message @@ -233,222 +227,241 @@ STR end describe "#nsfw" do - it 'returns MatchObject (true) if the post contains #nsfw (however capitalised)' do - status = FactoryGirl.build(:status_message, :text => "This message is #nSFw") + it "returns MatchObject (true) if the post contains #nsfw (however capitalised)" do + status = FactoryGirl.build(:status_message, text: "This message is #nSFw") expect(status.nsfw).to be_truthy end - it 'returns nil (false) if the post does not contain #nsfw' do - status = FactoryGirl.build(:status_message, :text => "This message is #sFW") + it "returns nil (false) if the post does not contain #nsfw" do + status = FactoryGirl.build(:status_message, text: "This message is #sFW") expect(status.nsfw).to be false end end - describe 'tags' do + describe "tags" do before do @object = FactoryGirl.build(:status_message) end - it_should_behave_like 'it is taggable' + it_should_behave_like "it is taggable" - it 'associates different-case tags to the same tag entry' do + it "associates different-case tags to the same tag entry" do assert_equal ActsAsTaggableOn.force_lowercase, true - msg_lc = FactoryGirl.build(:status_message, :text => '#newhere') - msg_uc = FactoryGirl.build(:status_message, :text => '#NewHere') - msg_cp = FactoryGirl.build(:status_message, :text => '#NEWHERE') + msg_lc = FactoryGirl.build(:status_message, text: "#newhere") + msg_uc = FactoryGirl.build(:status_message, text: "#NewHere") + msg_cp = FactoryGirl.build(:status_message, text: "#NEWHERE") - msg_lc.save; msg_uc.save; msg_cp.save + msg_lc.save + msg_uc.save + msg_cp.save tag_array = msg_lc.tags expect(msg_uc.tags).to match_array(tag_array) expect(msg_cp.tags).to match_array(tag_array) end - it 'should require tag name not be more than 255 characters long' do - message = "##{'a' * (255+1)}" - status_message = FactoryGirl.build(:status_message, :text => message) + it "should require tag name not be more than 255 characters long" do + message = "##{'a' * (255 + 1)}" + status_message = FactoryGirl.build(:status_message, text: message) expect(status_message).not_to be_valid end end describe "XML" do - let(:message) { FactoryGirl.build(:status_message, text: "I hate WALRUSES!", author: @user.person) } + let(:message) { FactoryGirl.build(:status_message, text: "I hate WALRUSES!", author: user.person) } let(:xml) { message.to_xml.to_s } let(:marshalled) { StatusMessage.from_xml(xml) } - it 'serializes the escaped, unprocessed message' do + it "serializes the escaped, unprocessed message" do text = "[url](http://example.org)" message.text = text expect(xml).to include Builder::XChar.encode(text) end - it 'serializes the message' do + it "serializes the message" do expect(xml).to include "I hate WALRUSES!" end - it 'serializes the author address' do - expect(xml).to include(@user.person.diaspora_handle) + it "serializes the author address" do + expect(xml).to include(user.person.diaspora_handle) end - describe '.from_xml' do - it 'marshals the message' do + describe ".from_xml" do + it "marshals the message" do expect(marshalled.text).to eq("I hate WALRUSES!") end - it 'marshals the guid' do + it "marshals the guid" do expect(marshalled.guid).to eq(message.guid) end - it 'marshals the author' do + it "marshals the author" do expect(marshalled.author).to eq(message.author) end - it 'marshals the diaspora_handle' do + it "marshals the diaspora_handle" do expect(marshalled.diaspora_handle).to eq(message.diaspora_handle) end end - context 'with some photos' do + context "with some photos" do before do message.photos << FactoryGirl.build(:photo) message.photos << FactoryGirl.build(:photo) end - it 'serializes the photos' do + it "serializes the photos" do expect(xml).to include "photo" expect(xml).to include message.photos.first.remote_photo_path end - describe '.from_xml' do - it 'marshals the photos' do + describe ".from_xml" do + it "marshals the photos" do expect(marshalled.photos.size).to eq(2) end - it 'handles existing photos' do + it "handles existing photos" do message.photos.each(&:save!) expect(marshalled).to be_valid end end end - context 'with a location' do + context "with a location" do before do message.location = FactoryGirl.build(:location) end - it 'serializes the location' do + it "serializes the location" do expect(xml).to include "location" expect(xml).to include "lat" expect(xml).to include "lng" end describe ".from_xml" do - it 'marshals the location' do + it "marshals the location" do expect(marshalled.location).to be_present end end end - context 'with a poll' do + context "with a poll" do before do message.poll = FactoryGirl.build(:poll) end - it 'serializes the poll' do + it "serializes the poll" do expect(xml).to include "poll" expect(xml).to include "question" expect(xml).to include "poll_answer" end describe ".from_xml" do - it 'marshals the poll' do + it "marshals the poll" do expect(marshalled.poll).to be_present end - it 'marshals the poll answers' do + it "marshals the poll answers" do expect(marshalled.poll.poll_answers.size).to eq(2) end end end end - describe '#after_dispatch' do + describe "#after_dispatch" do before do - @photos = [alice.build_post(:photo, :pending => true, :user_file=> File.open(photo_fixture_name)), - alice.build_post(:photo, :pending => true, :user_file=> File.open(photo_fixture_name))] - + @photos = [alice.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name)), + alice.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name))] @photos.each(&:save!) - - @status_message = alice.build_post(:status_message, :text => "the best pebble.") - @status_message.photos << @photos - + @status_message = alice.build_post(:status_message, text: "the best pebble.") + @status_message.photos << @photos @status_message.save! alice.add_to_streams(@status_message, alice.aspects) end - it 'sets pending to false on any attached photos' do + + it "sets pending to false on any attached photos" do @status_message.after_dispatch(alice) - expect(@photos.all?{|p| p.reload.pending}).to be false + expect(@photos.all? {|p| p.reload.pending }).to be false end - it 'dispatches any attached photos' do + + it "dispatches any attached photos" do expect(alice).to receive(:dispatch_post).twice @status_message.after_dispatch(alice) end end - describe 'oembed' do - before do - @youtube_url = "https://www.youtube.com/watch?v=3PtFwlKfvHI" - @message_text = "#{@youtube_url} is so cool. so is this link -> https://joindiaspora.com" - end + describe "oembed" do + let(:youtube_url) { "https://www.youtube.com/watch?v=3PtFwlKfvHI" } + let(:message_text) { "#{youtube_url} is so cool. so is this link -> https://joindiaspora.com" } + let(:status_message) { FactoryGirl.build(:status_message, text: message_text) } - it 'should queue a GatherOembedData if it includes a link' do - sm = FactoryGirl.build(:status_message, :text => @message_text) + it "should queue a GatherOembedData if it includes a link" do + status_message expect(Workers::GatherOEmbedData).to receive(:perform_async).with(instance_of(Fixnum), instance_of(String)) - sm.save + status_message.save end - describe '#contains_oembed_url_in_text?' do - it 'returns the oembed urls found in the raw message' do - sm = FactoryGirl.build(:status_message, :text => @message_text) - expect(sm.contains_oembed_url_in_text?).not_to be_nil - expect(sm.oembed_url).to eq(@youtube_url) + describe "#contains_oembed_url_in_text?" do + it "returns the oembed urls found in the raw message" do + expect(status_message.contains_oembed_url_in_text?).not_to be_nil + expect(status_message.oembed_url).to eq(youtube_url) end end end - describe 'opengraph' do - before do - @ninegag_url = "http://9gag.com/gag/a1AMW16" - @youtube_url = "https://www.youtube.com/watch?v=3PtFwlKfvHI" - @message_text = "#{@ninegag_url} is so cool. so is this link -> https://joindiaspora.com" - @oemessage_text = "#{@youtube_url} is so cool. so is this link -> https://joindiaspora.com" - end + describe "opengraph" do + let(:ninegag_url) { "http://9gag.com/gag/a1AMW16" } + let(:youtube_url) { "https://www.youtube.com/watch?v=3PtFwlKfvHI" } + let(:message_text) { "#{ninegag_url} is so cool. so is this link -> https://joindiaspora.com" } + let(:oemessage_text) { "#{youtube_url} is so cool. so is this link -> https://joindiaspora.com" } + let(:status_message) { build(:status_message, text: message_text) } - it 'should queue a GatherOpenGraphData if it includes a link' do - sm = FactoryGirl.build(:status_message, :text => @message_text) + it "should queue a GatherOpenGraphData if it includes a link" do + status_message expect(Workers::GatherOpenGraphData).to receive(:perform_async).with(instance_of(Fixnum), instance_of(String)) - sm.save + status_message.save end - describe '#contains_open_graph_url_in_text?' do - it 'returns the opengraph urls found in the raw message' do - sm = FactoryGirl.build(:status_message, :text => @message_text) - expect(sm.contains_open_graph_url_in_text?).not_to be_nil - expect(sm.open_graph_url).to eq(@ninegag_url) + describe "#contains_open_graph_url_in_text?" do + it "returns the opengraph urls found in the raw message" do + expect(status_message.contains_open_graph_url_in_text?).not_to be_nil + expect(status_message.open_graph_url).to eq(ninegag_url) end - it 'returns nil if the link is from trusted oembed provider' do - sm = FactoryGirl.build(:status_message, :text => @oemessage_text) - expect(sm.contains_open_graph_url_in_text?).to be_nil - expect(sm.open_graph_url).to be_nil + it "returns nil if the link is from trusted oembed provider" do + status_message = FactoryGirl.build(:status_message, text: oemessage_text) + expect(status_message.contains_open_graph_url_in_text?).to be_nil + expect(status_message.open_graph_url).to be_nil end end end describe "validation" do + let(:status_message) { build(:status_message, text: @message_text) } + it "should not be valid if the author is missing" do - sm = FactoryGirl.build(:status_message, text: @message_text) - sm.author = nil - expect(sm).not_to be_valid + status_message.author = nil + expect(status_message).not_to be_valid + end + end + + describe "#coordinates" do + let(:status_message) { build(:status_message, text: @message_text) } + + context "with location" do + let(:location) { build(:location) } + + it "should deliver address and coordinates" do + status_message.location = location + expect(status_message.post_location).to include(address: location.address, lat: location.lat, lng: location.lng) + end + end + + context "without location" do + it "should deliver empty address and coordinates" do + expect(status_message.post_location[:address]).to be_nil + expect(status_message.post_location[:lat]).to be_nil + expect(status_message.post_location[:lng]).to be_nil + end end end end