add a map subview

- add coordinates in post_presenter
- add map to the SPV if location is provided
- add leaflet.js to render map and marker of position
- make coordinates available in frontend
- add map scss
- make stream post location clickable and redirect to the SPV
- prevent render map if no location data is provided
- add tests for coordinates
- use the leaflet gem instead of the JS assets
(#5813)
This commit is contained in:
zaziemo 2015-07-17 16:58:53 +02:00 committed by realtin
parent f9a452265d
commit 298e195a8f
20 changed files with 424 additions and 318 deletions

View file

@ -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"

View file

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

View file

@ -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

View file

@ -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 &copy; <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors, " +
"<a href='http://creativecommons.org/licenses/by-sa/2.0/''>CC-BY-SA</a>, " +
"Imagery © <a href='http://mapbox.com'>Mapbox</a>";
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

View file

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

View file

@ -45,3 +45,4 @@
//= require osmlocator
//= require bootstrap-switch
//= require blueimp-gallery
//= require leaflet

View file

@ -42,6 +42,10 @@
@import 'single-post-view';
@import 'new_styles/poll';
/* map*/
@import 'leaflet';
@import 'map';
/* conversations */
@import 'conversations';

View file

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

View file

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

View file

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

View file

@ -99,4 +99,5 @@
<div id="body" class="row">
<div id="real-post-content" class="post-content col-md-12">
</div>
<div class="map col-md-12"></div>
</div>

View file

@ -1,5 +1,5 @@
{{#if location}}
<div class='near-from'>
{{ t "publisher.near_from" location=location}}
<a href="/posts/{{id}}">{{ t "publisher.near_from" location=location}}</a>
</div>
{{/if}}

View file

@ -0,0 +1,4 @@
{{#if location}}
<div id="map">
</div>
{{/if}}

View file

@ -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

View file

@ -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?

View file

@ -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?,

View file

@ -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

View file

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

View file

@ -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

View file

@ -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)<script> alert('xss should be federated');</script>"
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 "<raw_message>I hate WALRUSES!</raw_message>"
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