diff --git a/Gemfile b/Gemfile index 0dd8f16ab..1017a84ac 100644 --- a/Gemfile +++ b/Gemfile @@ -128,6 +128,9 @@ gem "rails-i18n", "4.0.4" gem "markerb", "1.0.2" 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 250f3e955..a73133859 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -409,6 +409,7 @@ GEM actionpack (>= 3.0.0) activesupport (>= 3.0.0) kgio (2.9.3) + leaflet-rails (0.7.4) listen (3.0.3) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) @@ -833,6 +834,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.0.2) 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..20ddadb0d 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'); @@ -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_map.js b/app/assets/javascripts/app/views/location_map.js new file mode 100644 index 000000000..e499e2371 --- /dev/null +++ b/app/assets/javascripts/app/views/location_map.js @@ -0,0 +1,34 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later + +app.views.LocationMap = app.views.Content.extend({ + templateName: "status-message-map", + + map: function() { + var coordinates = this.model.get("coordinates"); + + // if (coordinates != "" && tileserver.enable) { // for when the tileserver is set via the diaspora.yml + if (coordinates.lat) { + var tileLayerSource = "https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}"; + var map = L.map("map").setView([coordinates.lat, coordinates.lng], 16); + var attribution = "Map data © OpenStreetMap contributors, " + + "CC-BY-SA, " + + "Imagery © Mapbox"; + + L.tileLayer(tileLayerSource, { + attribution: attribution, + maxZoom: 18, + id: "zaziemo.mpn66kn8", + accessToken: "pk.eyJ1IjoiemF6aWVtbyIsImEiOiI3ODVjMzVjNmM2ZTU3YWM3YTE5YWYwMTRhODljM2M1MSJ9.-nVgyS4PLnV4m9YkvMB5wA" + }).addTo(map); + + var markerOnMap = L.marker(coordinates).addTo(map); + + return map; + } + }, + + postRenderTemplate : function(){ + _.defer(_.bind(this.map, this)); + } +}); +// @license-end 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..92e5cb29e 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 @@ -11,7 +11,8 @@ app.views.SinglePostContent = app.views.Base.extend({ ".oembed" : "oEmbedView", ".opengraph" : "openGraphView", ".status-message-location" : "postLocationStreamView", - ".poll": "pollView" + ".map" : "postMapView", + '.poll': 'pollView', }, initialize : function() { @@ -20,6 +21,7 @@ app.views.SinglePostContent = app.views.Base.extend({ this.oEmbedView = new app.views.OEmbed({model : this.model}); this.openGraphView = new app.views.SPVOpenGraph({model : this.model}); this.postContentView = new app.views.ExpandedStatusMessage({model: this.model}); + this.postMapView = new app.views.LocationMap({model: this.model}); this.pollView = new app.views.Poll({ model: this.model }); }, 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 b47584a22..edabd4527 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..f210f8dbb --- /dev/null +++ b/app/assets/stylesheets/map.scss @@ -0,0 +1,13 @@ +#map { + height: 180px; + position: relative; + overflow: hidden; +} + +.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..71ca37235 100644 --- a/app/assets/stylesheets/single-post-view.scss +++ b/app/assets/stylesheets/single-post-view.scss @@ -13,6 +13,7 @@ color: lighten($text-grey,10%); font-size: 12px; .post-time a { color: $text-grey; } + .near-from a { color: $text-grey; } .post_scope { margin-right: 5px; } .status-message-location { padding-top: 2px; diff --git a/app/assets/stylesheets/stream_element.scss b/app/assets/stylesheets/stream_element.scss index 9b2861c5a..1cf764c64 100644 --- a/app/assets/stylesheets/stream_element.scss +++ b/app/assets/stylesheets/stream_element.scss @@ -83,7 +83,7 @@ float: left; margin-top: 6px; } - .status-message-location .near-from { + .status-message-location .near-from a { font-size: $font-size-small; color: $text-grey; } 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..4da6a3ead 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 @@ -99,4 +99,5 @@
+
diff --git a/app/assets/templates/status-message-location_tpl.jst.hbs b/app/assets/templates/status-message-location_tpl.jst.hbs index c6c7ea7ec..0d648125c 100644 --- a/app/assets/templates/status-message-location_tpl.jst.hbs +++ b/app/assets/templates/status-message-location_tpl.jst.hbs @@ -1,5 +1,5 @@ {{#if location}}
- {{ t "publisher.near_from" location=location}} + {{ t "publisher.near_from" location=location}}
{{/if}} diff --git a/app/assets/templates/status-message-map_tpl.jst.hbs b/app/assets/templates/status-message-map_tpl.jst.hbs new file mode 100644 index 000000000..87207d39c --- /dev/null +++ b/app/assets/templates/status-message-map_tpl.jst.hbs @@ -0,0 +1,4 @@ +{{#if location}} +
+
+{{/if}} diff --git a/app/models/reshare.rb b/app/models/reshare.rb index 93803af1d..f3cbdb2ed 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 @@ -49,6 +48,10 @@ class Reshare < Post absolute_root.try(:location).try(:address) end + def coordinates + {lat: absolute_root.try(:location).try(:lat), lng: absolute_root.try(:location).try(:lng)} + end + def poll absolute_root.try(:poll) || super end diff --git a/app/models/status_message.rb b/app/models/status_message.rb index 4dacee42c..b69a682d6 100644 --- a/app/models/status_message.rb +++ b/app/models/status_message.rb @@ -162,6 +162,10 @@ class StatusMessage < Post location.try(:address) end + def coordinates + {lat: location.try(:lat), lng: location.try(:lng)} + end + protected def presence_of_content if text_and_photos_blank? diff --git a/app/presenters/post_presenter.rb b/app/presenters/post_presenter.rb index b1df1a917..ea77b1530 100644 --- a/app/presenters/post_presenter.rb +++ b/app/presenters/post_presenter.rb @@ -32,6 +32,7 @@ class PostPresenter < BasePresenter root: root, title: title, address: @post.address, + coordinates: @post.coordinates, poll: @post.poll, already_participated_in_poll: already_participated_in_poll, participation: participate?, diff --git a/spec/factories.rb b/spec/factories.rb index 1f99c0f0b..a7cddc2b7 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -140,9 +140,10 @@ FactoryGirl.define do end factory(:location) do - address "unicorn city" - lat 1 - lng 2 + address "Starco Mart, Mission Street, West SoMa, San Francisco, San Francisco "\ + "City and County, Kalifornien, 94103, Vereinigte Staaten von Amerika" + lat 37.78 + lng -122.41 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..218bc4e0f 100644 --- a/spec/javascripts/app/views/content_view_spec.js +++ b/spec/javascripts/app/views/content_view_spec.js @@ -24,5 +24,11 @@ describe("app.views.Content", function(){ this.post.set({post_type : "Reshare"}); expect(this.view.presenter().isReshare).toBeTruthy(); }); + + // it("provides coordinates", function(){ + // this.post.location; + // console.log(this.view.presenter()); + // console.log(this.post.location); + // }); }); }); diff --git a/spec/models/reshare_spec.rb b/spec/models/reshare_spec.rb index e235c4066..3661bae94 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,78 @@ 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 ".coordinates" 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 coordinates" do + status_message.location = location + expect(reshare.coordinates).to include(lat: location.lat, lng: location.lng) + end + end + + context "without location" do + it "should deliver empty coordinates" do + expect(reshare.coordinates[:lat]).to be_nil + expect(reshare.coordinates[: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..6b1523bce 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) + status_message_2 = 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,240 @@ 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 coordinates" do + status_message.location = location + expect(status_message.coordinates).to include(lat: location.lat, lng: location.lng) + end + end + + context "without location" do + it "should deliver empty coordinates" do + expect(status_message.coordinates[:lat]).to be_nil + expect(status_message.coordinates[:lng]).to be_nil + end end end end