Merge pull request #6256 from TeamDeltaQuadrant/5813-show-geolocation-on-osm

5813 show geolocation on osm
This commit is contained in:
Steffen van Bergerem 2015-09-23 01:41:31 +02:00
commit b40d5362cf
29 changed files with 655 additions and 334 deletions

View file

@ -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) * 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) * 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) * 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 # 0.5.4.0

View file

@ -128,6 +128,9 @@ gem "rails-i18n", "4.0.5"
gem "markerb", "1.1.0" gem "markerb", "1.1.0"
gem "messagebus_ruby_api", "1.0.3" gem "messagebus_ruby_api", "1.0.3"
# Map
gem "leaflet-rails", "0.7.4"
# Parsing # Parsing
gem "nokogiri", "1.6.6.2" gem "nokogiri", "1.6.6.2"

View file

@ -418,6 +418,7 @@ GEM
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
kgio (2.10.0) kgio (2.10.0)
leaflet-rails (0.7.4)
listen (3.0.3) listen (3.0.3)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9) rb-inotify (>= 0.9)
@ -841,6 +842,7 @@ DEPENDENCIES
jshintrb (= 0.3.0) jshintrb (= 0.3.0)
json (= 1.8.3) json (= 1.8.3)
json-schema (= 2.5.1) json-schema (= 2.5.1)
leaflet-rails (= 0.7.4)
logging-rails (= 0.5.0) logging-rails (= 0.5.0)
markerb (= 1.1.0) markerb (= 1.1.0)
messagebus_ruby_api (= 1.0.3) messagebus_ruby_api (= 1.0.3)

View file

@ -29,7 +29,6 @@ app.views.Content = app.views.Base.extend({
return photos; return photos;
}, },
expandPost: function(evt) { expandPost: function(evt) {
var el = $(this.el).find('.collapsible'); var el = $(this.el).find('.collapsible');
el.removeClass('collapsed').addClass('opened'); el.removeClass('collapsed').addClass('opened');
@ -40,8 +39,8 @@ app.views.Content = app.views.Base.extend({
}, },
location: function(){ location: function(){
var address = this.model.get('address')? this.model.get('address') : ''; var location = this.model.get("location")? this.model.get("location") : "";
return address; return location;
}, },
collapseOversized : function() { collapseOversized : function() {
@ -155,4 +154,5 @@ app.views.SPVOpenGraph = app.views.OpenGraph.extend({
// override with nothing // override with nothing
} }
}); });
// @license-end // @license-end

View file

@ -1,7 +1,54 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.views.LocationStream = app.views.Content.extend({ 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 &copy; <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors, " +
"rendering <a href='http://giscience.uni-hd.de/'>" +
"GIScience Research Group @ Heidelberg University</a>",
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 &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='https://www.mapbox.com'>Mapbox</a>",
maxZoom: 18,
});
}
tiles.addTo(map);
L.marker(location).addTo(map);
mapContainer.removeClass("empty");
return map;
}
} else {
mapContainer.toggle();
}
}
}); });
// @license-end // @license-end

View file

@ -1,6 +1,10 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.views.SinglePostContent = app.views.Base.extend({ app.views.SinglePostContent = app.views.Base.extend({
events: {
"click .near-from": "toggleMap"
},
templateName: "single-post-viewer/single-post-content", templateName: "single-post-viewer/single-post-content",
tooltipSelector: "time, .post_scope", tooltipSelector: "time, .post_scope",
@ -10,8 +14,7 @@ app.views.SinglePostContent = app.views.Base.extend({
"#real-post-content" : "postContentView", "#real-post-content" : "postContentView",
".oembed" : "oEmbedView", ".oembed" : "oEmbedView",
".opengraph" : "openGraphView", ".opengraph" : "openGraphView",
".status-message-location" : "postLocationStreamView", ".poll": "pollView",
".poll": "pollView"
}, },
initialize : function() { initialize : function() {
@ -23,8 +26,59 @@ app.views.SinglePostContent = app.views.Base.extend({
this.pollView = new app.views.Poll({ model: this.model }); this.pollView = new app.views.Poll({ model: this.model });
}, },
postLocationStreamView : function(){ map : function(){
return new app.views.LocationStream({ model : this.model}); 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 &copy; <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors, " +
"rendering <a href='http://giscience.uni-hd.de/'>" +
"GIScience Research Group @ Heidelberg University</a>",
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 &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='https://www.mapbox.com'>Mapbox</a>",
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() { presenter : function() {
@ -37,6 +91,10 @@ app.views.SinglePostContent = app.views.Base.extend({
showPost : function() { showPost : function() {
return (app.currentUser.get("showNsfw")) || !this.model.get("nsfw"); return (app.currentUser.get("showNsfw")) || !this.model.get("nsfw");
},
postRenderTemplate : function(){
_.defer(_.bind(this.map, this));
} }
}); });
// @license-end // @license-end

View file

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

View file

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

View file

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

View file

@ -23,6 +23,14 @@
padding-left: 10px; padding-left: 10px;
} }
} }
.near-from {
color: $text-grey;
font-size: 12px;
margin: 10px 20px 0px 15px;
}
.mapContainer {
margin: 10px 20px 0px 15px;
}
.row.reshare { .row.reshare {
border-top: 1px solid lighten($border-grey,5%); border-top: 1px solid lighten($border-grey,5%);
padding-top: 10px; padding-top: 10px;

View file

@ -83,10 +83,13 @@
float: left; float: left;
margin-top: 6px; margin-top: 6px;
} }
.status-message-location .near-from { .status-message-location {
font-size: $font-size-small; font-size: $font-size-small;
color: $text-grey; color: $text-grey;
} }
.leaflet-control-zoom {
display: block;
}
.grey { color: $text-grey; } .grey { color: $text-grey; }
.post-content p:last-of-type { margin-bottom: 0; } .post-content p:last-of-type { margin-bottom: 0; }
.nsfw-shield { .nsfw-shield {

View file

@ -57,7 +57,6 @@
{{t "stream.via" provider=provider_display_name}} {{t "stream.via" provider=provider_display_name}}
{{/if}} {{/if}}
{{/if}} {{/if}}
<div class="status-message-location" />
</div> </div>
{{#unless root}} {{#unless root}}
<div id="single-post-moderation" /> <div id="single-post-moderation" />
@ -68,6 +67,14 @@
<div id="single-post-actions" class="col-md-4" /> <div id="single-post-actions" class="col-md-4" />
{{/unless}} {{/unless}}
</div> </div>
{{#if location.lat}}
<div class="row">
<div class='near-from'>
{{t "publisher.near_from" location=location.address}}
</div>
<div class="mapContainer small-map"></div>
</div>
{{/if}}
{{#if root}} {{#if root}}
<div class="row reshare"> <div class="row reshare">
<div class="col-md-8" id="reshare-info"> <div class="col-md-8" id="reshare-info">

View file

@ -1,5 +1,8 @@
{{#if location}} {{#if location.address}}
<div class='near-from'> <div class='near-from'>
{{ t "publisher.near_from" location=location}} {{t "publisher.near_from" location=location.address}}
</div>
<div>
<div class="mapContainer empty"></div>
</div> </div>
{{/if}} {{/if}}

View file

@ -145,7 +145,8 @@ class ApplicationController < ActionController::Base
def gon_set_appconfig def gon_set_appconfig
gon.push(appConfig: { gon.push(appConfig: {
chat: {enabled: AppConfig.chat.enabled?}, chat: {enabled: AppConfig.chat.enabled?},
settings: {podname: AppConfig.settings.pod_name} settings: {podname: AppConfig.settings.pod_name},
map: {mapbox: AppConfig.map.mapbox}
}) })
end end

View file

@ -3,7 +3,6 @@
# the COPYRIGHT file. # the COPYRIGHT file.
class Reshare < Post class Reshare < Post
belongs_to :root, :class_name => 'Post', :foreign_key => :root_guid, :primary_key => :guid belongs_to :root, :class_name => 'Post', :foreign_key => :root_guid, :primary_key => :guid
validate :root_must_be_public validate :root_must_be_public
validates_presence_of :root, :on => :create validates_presence_of :root, :on => :create
@ -45,8 +44,12 @@ class Reshare < Post
absolute_root.try(:photos) || super absolute_root.try(:photos) || super
end end
def address def post_location
absolute_root.try(:location).try(:address) {
address: absolute_root.try(:location).try(:address),
lat: absolute_root.try(:location).try(:lat),
lng: absolute_root.try(:location).try(:lng)
}
end end
def poll def poll

View file

@ -158,8 +158,12 @@ class StatusMessage < Post
self.open_graph_url = self.message.urls[0] self.open_graph_url = self.message.urls[0]
end end
def address def post_location
location.try(:address) {
address: location.try(:address),
lat: location.try(:lat),
lng: location.try(:lng)
}
end end
protected protected

View file

@ -31,7 +31,7 @@ class PostPresenter < BasePresenter
photos: build_photos_json, photos: build_photos_json,
root: root, root: root,
title: title, title: title,
address: @post.address, location: @post.post_location,
poll: @post.poll, poll: @post.poll,
already_participated_in_poll: already_participated_in_poll, already_participated_in_poll: already_participated_in_poll,
participation: participate?, participation: participate?,

View file

@ -46,6 +46,7 @@
"HandlebarsTemplates", "HandlebarsTemplates",
"ImagePaths", "ImagePaths",
"jsxc", "jsxc",
"L",
"MBP", "MBP",
"Routes", "Routes",
"OSM", "OSM",

View file

@ -76,6 +76,11 @@ defaults:
log: log:
file: 'log/vines.log' file: 'log/vines.log'
level: 'info' level: 'info'
map:
mapbox:
enabled: false
id:
access_token:
privacy: privacy:
jquery_cdn: false jquery_cdn: false
google_analytics_key: google_analytics_key:

View file

@ -327,6 +327,20 @@ configuration: ## Section
## The debug level logs all XML sent and received by the server. ## The debug level logs all XML sent and received by the server.
#level: 'info' #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. ## Settings potentially affecting the privacy of your users.
privacy: ## Section privacy: ## Section

View file

@ -140,9 +140,9 @@ FactoryGirl.define do
end end
factory(:location) do factory(:location) do
address "unicorn city" address "Fernsehturm Berlin, Berlin, Germany"
lat 1 lat 52.520645
lng 2 lng 13.409779
end end
factory(:poll) do factory(:poll) do

View file

@ -24,5 +24,10 @@ describe("app.views.Content", function(){
this.post.set({post_type : "Reshare"}); this.post.set({post_type : "Reshare"});
expect(this.view.presenter().isReshare).toBeTruthy(); expect(this.view.presenter().isReshare).toBeTruthy();
}); });
it("provides location", function(){
this.post.set({location : factory.location()});
expect(this.view.presenter().location).toEqual(factory.location());
});
}); });
}); });

View file

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

View file

@ -1,6 +1,5 @@
describe("app.views.Location", function(){ describe("app.views.Location", function(){
beforeEach(function(){ beforeEach(function(){
OSM = {};
OSM.Locator = function(){return { getAddress:function(){}}}; OSM.Locator = function(){return { getAddress:function(){}}};
this.view = new app.views.Location(); this.view = new app.views.Location();

View file

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

View file

@ -148,6 +148,14 @@ var factory = {
}, overrides); }, 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) { post : function(overrides) {
var defaultAttrs = _.extend(factory.postAttrs(), {"author" : this.author()}); var defaultAttrs = _.extend(factory.postAttrs(), {"author" : this.author()});
return new app.models.Post(_.extend(defaultAttrs, overrides)); return new app.models.Post(_.extend(defaultAttrs, overrides));

View file

@ -1,33 +1,33 @@
require 'spec_helper' require "spec_helper"
describe Reshare, :type => :model do describe Reshare, type: :model do
it 'has a valid Factory' do it "has a valid Factory" do
expect(FactoryGirl.build(:reshare)).to be_valid expect(FactoryGirl.build(:reshare)).to be_valid
end end
it 'requires root' do it "requires root" do
reshare = FactoryGirl.build(:reshare, :root => nil) reshare = FactoryGirl.build(:reshare, root: nil)
expect(reshare).not_to be_valid expect(reshare).not_to be_valid
end end
it 'require public root' do it "require public root" do
reshare = FactoryGirl.build(:reshare, :root => FactoryGirl.create(:status_message, :public => false)) reshare = FactoryGirl.build(:reshare, root: FactoryGirl.create(:status_message, public: false))
expect(reshare).not_to be_valid 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 end
it 'forces public' do it "forces public" do
expect(FactoryGirl.create(:reshare, :public => false).public).to be true expect(FactoryGirl.create(:reshare, public: false).public).to be true
end end
describe "#root_diaspora_id" do 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 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) expect(reshare.root_diaspora_id).to eq(bob.person.diaspora_handle)
end end
it "should be nil if no root found" do 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 reshare.root = nil
expect(reshare.root_diaspora_id).to be_nil expect(reshare.root_diaspora_id).to be_nil
end end
@ -37,195 +37,188 @@ describe Reshare, :type => :model do
let(:receive_reshare) { @reshare.receive(@root.author.owner, @reshare.author) } let(:receive_reshare) { @reshare.receive(@root.author.owner, @reshare.author) }
before do 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 @root = @reshare.root
end end
it 'increments the reshare count' do it "increments the reshare count" do
receive_reshare receive_reshare
expect(@root.resharers.count).to eq(1) expect(@root.resharers.count).to eq(1)
end 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 receive_reshare
expect(@root.resharers).to include(@reshare.author) expect(@root.resharers).to include(@reshare.author)
end 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 bob.share_with @reshare.author, bob.aspects.first
expect { expect {
Timeout.timeout(5) do 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 end
}.not_to raise_error }.not_to raise_error
end end
end end
describe '#nsfw' do describe "#nsfw" do
before do let(:sfw) { build(:status_message, author: alice.person, public: true) }
sfw = FactoryGirl.build(:status_message, :author => alice.person, :public => true) let(:nsfw) { build(:status_message, author: alice.person, public: true, text: "This is #nsfw") }
nsfw = FactoryGirl.build(:status_message, :author => alice.person, :public => true, :text => "This is #nsfw") let(:sfw_reshare) { build(:reshare, root: sfw) }
@sfw_reshare = FactoryGirl.build(:reshare, :root => sfw) let(:nsfw_reshare) { build(:reshare, root: nsfw) }
@nsfw_reshare = FactoryGirl.build(:reshare, :root => nsfw)
end
it 'deletates #nsfw to the root post' do it "deletates #nsfw to the root post" do
expect(@sfw_reshare.nsfw).not_to be true expect(sfw_reshare.nsfw).not_to be true
expect(@nsfw_reshare.nsfw).to be_truthy expect(nsfw_reshare.nsfw).to be_truthy
end end
end end
describe '#poll' do describe "#poll" do
before do let(:root_post) { create(:status_message_with_poll, public: true) }
@root_post = FactoryGirl.create(:status_message_with_poll, public: true) let(:reshare) { create(:reshare, root: root_post) }
@reshare = FactoryGirl.create(:reshare, root: @root_post)
end
it 'contains root poll' do it "contains root poll" do
expect(@reshare.poll).to eq @root_post.poll expect(reshare.poll).to eq root_post.poll
end end
end end
describe '#notification_type' do describe "#notification_type" do
before do let(:status_message) { build(:status_message, author: alice.person, public: true) }
sm = FactoryGirl.build(:status_message, :author => alice.person, :public => true) let(:reshare) { build(:reshare, root: status_message) }
@reshare = FactoryGirl.build(:reshare, :root => sm)
end it "does not return anything for non-author of the original post" do
it 'does not return anything for non-author of the original post' do expect(reshare.notification_type(bob, reshare.author)).to be_nil
expect(@reshare.notification_type(bob, @reshare.author)).to be_nil
end end
it 'returns "Reshared" for the original post author' do it "returns 'Reshared' for the original post author" do
expect(@reshare.notification_type(alice, @reshare.author)).to eq(Notifications::Reshared) expect(reshare.notification_type(alice, reshare.author)).to eq(Notifications::Reshared)
end end
it 'does not error out if the root was deleted' do it "does not error out if the root was deleted" do
@reshare.root = nil reshare.root = nil
expect { expect {
@reshare.notification_type(alice, @reshare.author) reshare.notification_type(alice, reshare.author)
}.to_not raise_error }.to_not raise_error
end end
end end
describe '#absolute_root' do describe "#absolute_root" do
before do before do
@sm = FactoryGirl.build(:status_message, :author => alice.person, :public => true) @status_message = FactoryGirl.build(:status_message, author: alice.person, public: true)
rs1 = FactoryGirl.build(:reshare, :root=>@sm) reshare_1 = FactoryGirl.build(:reshare, root: @status_message)
rs2 = FactoryGirl.build(:reshare, :root=>rs1) reshare_2 = FactoryGirl.build(:reshare, root: reshare_1)
@rs3 = FactoryGirl.build(:reshare, :root=>rs2) @reshare_3 = FactoryGirl.build(:reshare, root: reshare_2)
sm = FactoryGirl.create(:status_message, :author => alice.person, :public => true) status_message = FactoryGirl.create(:status_message, author: alice.person, public: true)
rs1 = FactoryGirl.create(:reshare, :root => sm) reshare_1 = FactoryGirl.create(:reshare, root: status_message)
@of_deleted = FactoryGirl.build(:reshare, :root => rs1) @of_deleted = FactoryGirl.build(:reshare, root: reshare_1)
sm.destroy status_message.destroy
rs1.reload reshare_1.reload
end end
it 'resolves root posts to the top level' do it "resolves root posts to the top level" do
expect(@rs3.absolute_root).to eq(@sm) expect(@reshare_3.absolute_root).to eq(@status_message)
end end
it 'can handle deleted reshares' do it "can handle deleted reshares" do
expect(@of_deleted.absolute_root).to be_nil expect(@of_deleted.absolute_root).to be_nil
end end
it 'is used everywhere' do it "is used everywhere" do
expect(@rs3.message).to eq @sm.message expect(@reshare_3.message).to eq @status_message.message
expect(@of_deleted.message).to be_nil 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(@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(@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(@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(@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(@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 expect(@of_deleted.address).to be_nil
end end
end end
describe "XML" do describe "XML" do
before do let(:reshare) { build(:reshare) }
@reshare = FactoryGirl.build(:reshare) let(:xml) { reshare.to_xml.to_s }
@xml = @reshare.to_xml.to_s
end
context 'serialization' do context "serialization" do
it 'serializes root_diaspora_id' do it "serializes root_diaspora_id" do
expect(@xml).to include("root_diaspora_id") expect(xml).to include("root_diaspora_id")
expect(@xml).to include(@reshare.author.diaspora_handle) expect(xml).to include(reshare.author.diaspora_handle)
end end
it 'serializes root_guid' do it "serializes root_guid" do
expect(@xml).to include("root_guid") expect(xml).to include("root_guid")
expect(@xml).to include(@reshare.root.guid) expect(xml).to include(reshare.root.guid)
end end
end end
context 'marshalling' do context "marshalling" do
context 'local' do let(:root_object) { reshare.root }
before do
@original_author = @reshare.root.author context "local" do
@root_object = @reshare.root let(:original_author) { reshare.root.author }
it "marshals the guid" do
expect(Reshare.from_xml(xml).root_guid).to eq(root_object.guid)
end end
it 'marshals the guid' do it "fetches the root post from root_guid" do
expect(Reshare.from_xml(@xml).root_guid).to eq(@root_object.guid) expect(Reshare.from_xml(xml).root).to eq(root_object)
end end
it 'fetches the root post from root_guid' do it "fetches the root author from root_diaspora_id" do
expect(Reshare.from_xml(@xml).root).to eq(@root_object) expect(Reshare.from_xml(xml).root.author).to eq(original_author)
end
it 'fetches the root author from root_diaspora_id' do
expect(Reshare.from_xml(@xml).root.author).to eq(@original_author)
end end
end end
describe 'destroy' do describe "destroy" do
it 'allows you to destroy the reshare if the root post is missing' do it "allows you to destroy the reshare if the root post is missing" do
reshare = FactoryGirl.build(:reshare) reshare
reshare.root = nil reshare.root = nil
expect{ expect {
reshare.destroy reshare.destroy
}.to_not raise_error }.to_not raise_error
end end
end end
context 'remote' do context "remote" do
before do before do
@root_object = @reshare.root # root_object = reshare.root
@root_object.delete root_object.delete
@response = double @response = double
allow(@response).to receive(:status).and_return(200) allow(@response).to receive(:status).and_return(200)
allow(@response).to receive(:success?).and_return(true) allow(@response).to receive(:success?).and_return(true)
end end
it 'fetches the root author from root_diaspora_id' do it "fetches the root author from root_diaspora_id" do
@original_profile = @reshare.root.author.profile.dup @original_profile = reshare.root.author.profile.dup
@reshare.root.author.profile.delete reshare.root.author.profile.delete
@original_author = @reshare.root.author.dup @original_author = reshare.root.author.dup
@reshare.root.author.delete reshare.root.author.delete
@original_author.profile = @original_profile @original_author.profile = @original_profile
expect(Person).to receive(:find_or_fetch_by_identifier).and_return(@original_author) 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( expect(Faraday.default_connection).to receive(:get).with(
URI.join( URI.join(
@original_author.url, @original_author.url,
Rails.application.routes.url_helpers.short_post_path( Rails.application.routes.url_helpers.short_post_path(
@root_object.guid, root_object.guid,
format: "xml" format: "xml"
) )
) )
).and_return(@response) ).and_return(@response)
Reshare.from_xml(@xml) Reshare.from_xml(xml)
end end
context "fetching post" do context "fetching post" do
@ -234,7 +227,7 @@ describe Reshare, :type => :model do
expect(Faraday.default_connection).to receive(:get).and_return(@response) expect(Faraday.default_connection).to receive(:get).and_return(@response)
expect { expect {
Reshare.from_xml(@xml) Reshare.from_xml(xml)
}.to raise_error(Diaspora::PostNotFetchable) }.to raise_error(Diaspora::PostNotFetchable)
end end
@ -244,57 +237,79 @@ describe Reshare, :type => :model do
expect(Faraday.default_connection).to receive(:get).and_return(@response) expect(Faraday.default_connection).to receive(:get).and_return(@response)
expect { expect {
Reshare.from_xml(@xml) Reshare.from_xml(xml)
}.to raise_error RuntimeError }.to raise_error RuntimeError
end end
end end
context 'saving the post' do context "saving the post" do
before 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( allow(Faraday.default_connection).to receive(:get).with(
URI.join( URI.join(
@reshare.root.author.url, reshare.root.author.url,
Rails.application.routes.url_helpers.short_post_path( Rails.application.routes.url_helpers.short_post_path(
@root_object.guid, root_object.guid,
format: "xml" format: "xml"
) )
) )
).and_return(@response) ).and_return(@response)
end end
it 'fetches the root post from root_guid' do it "fetches the root post from root_guid" do
root = Reshare.from_xml(@xml).root root = Reshare.from_xml(xml).root
[:text, :guid, :diaspora_handle, :type, :public].each do |attr| %i(text guid diaspora_handle type public).each do |attr|
expect(root.send(attr)).to eq(@reshare.root.send(attr)) expect(root.send(attr)).to eq(reshare.root.send(attr))
end end
end end
it 'correctly saves the type' do it "correctly saves the type" do
expect(Reshare.from_xml(@xml).root.reload.type).to eq("StatusMessage") expect(Reshare.from_xml(xml).root.reload.type).to eq("StatusMessage")
end end
it 'correctly sets the author' do it "correctly sets the author" do
@original_author = @reshare.root.author @original_author = reshare.root.author
expect(Reshare.from_xml(@xml).root.reload.author.reload).to eq(@original_author) expect(Reshare.from_xml(xml).root.reload.author.reload).to eq(@original_author)
end end
it 'verifies that the author of the post received is the same as the author in the reshare xml' do 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 @original_author = reshare.root.author.dup
@xml = @reshare.to_xml.to_s xml = reshare.to_xml.to_s
different_person = FactoryGirl.build(:person) different_person = FactoryGirl.build(:person)
expect(Person).to receive(:find_or_fetch_by_identifier).and_return(different_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) allow(different_person).to receive(:url).and_return(@original_author.url)
expect{ expect {
Reshare.from_xml(@xml) Reshare.from_xml(xml)
}.to raise_error /^Diaspora ID \(.+\) in the root does not match the Diaspora ID \(.+\) specified in the reshare!$/ }.to raise_error /^Diaspora ID \(.+\) in the root does not match the Diaspora ID \(.+\) specified in the reshare!$/
end end
end end
end 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 end

View file

@ -2,56 +2,54 @@
# licensed under the Affero General Public License version 3 or later. See # licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file. # the COPYRIGHT file.
require 'spec_helper' require "spec_helper"
describe StatusMessage, :type => :model do describe StatusMessage, type: :model do
include PeopleHelper include PeopleHelper
before do let!(:user) { alice }
@user = alice let!(:aspect) { user.aspects.first }
@aspect = @user.aspects.first let(:status) { build(:status_message) }
end
describe 'scopes' do describe "scopes" do
describe '.where_person_is_mentioned' do describe ".where_person_is_mentioned" do
it 'returns status messages where the given person is mentioned' do it "returns status messages where the given person is mentioned" do
@bo = bob.person @bob = bob.person
@test_string = "@{Daniel; #{@bo.diaspora_handle}} can mention people like Raph" @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 ) expect(StatusMessage.where_person_is_mentioned(bob).count).to eq(2)
FactoryGirl.create(:status_message, :text => @test_string )
FactoryGirl.create(:status_message)
expect(StatusMessage.where_person_is_mentioned(@bo).count).to eq(2)
end end
end end
context "tag_streams" do context "tag_streams" do
before do before do
@sm1 = FactoryGirl.create(:status_message, :text => "#hashtag" , :public => true) @status_message_1 = FactoryGirl.create(:status_message, text: "#hashtag", public: true)
@sm2 = FactoryGirl.create(:status_message, :text => "#hashtag" ) @status_message_2 = FactoryGirl.create(:status_message, text: "#hashtag")
@sm3 = FactoryGirl.create(:status_message, :text => "hashtags are #awesome", :public => true ) @status_message_3 = FactoryGirl.create(:status_message, text: "hashtags are #awesome", public: true)
@sm4 = FactoryGirl.create(:status_message, :text => "hashtags are #awesome" ) @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 end
describe '.tag_steam' do describe ".tag_steam" do
it 'returns status messages tagged with the tag' do it "returns status messages tagged with the tag" do
tag_stream = StatusMessage.send(:tag_stream, [@tag_id]) tag_stream = StatusMessage.send(:tag_stream, [@tag_id])
expect(tag_stream).to include @sm1 expect(tag_stream).to include @status_message_1
expect(tag_stream).to include @sm2 expect(tag_stream).to include @status_message_2
end end
end end
describe '.public_tag_stream' do describe ".public_tag_stream" do
it 'returns public status messages tagged with the tag' do it "returns public status messages tagged with the tag" do
expect(StatusMessage.public_tag_stream([@tag_id])).to eq([@sm1]) expect(StatusMessage.public_tag_stream([@tag_id])).to eq([@status_message_1])
end end
end end
describe '.user_tag_stream' do describe ".user_tag_stream" do
it 'returns tag stream thats owned or visible by' do it "returns tag stream thats owned or visible by" do
relation = double relation = double
expect(StatusMessage).to receive(:owned_or_visible_by_user).with(bob).and_return(relation) expect(StatusMessage).to receive(:owned_or_visible_by_user).with(bob).and_return(relation)
expect(relation).to receive(:tag_stream).with([@tag_id]) expect(relation).to receive(:tag_stream).with([@tag_id])
@ -63,48 +61,45 @@ describe StatusMessage, :type => :model do
end end
describe ".guids_for_author" do describe ".guids_for_author" do
it 'returns an array of the status_message guids' do it "returns an array of the status_message guids" do
sm1 = FactoryGirl.create(:status_message, :author => alice.person) status_message_1 = FactoryGirl.create(:status_message, author: alice.person)
sm2 = FactoryGirl.create(:status_message, :author => bob.person) FactoryGirl.create(:status_message, author: bob.person)
guids = StatusMessage.guids_for_author(alice.person) guids = StatusMessage.guids_for_author(alice.person)
expect(guids).to eq([sm1.guid]) expect(guids).to eq([status_message_1.guid])
end end
end end
describe '.before_validation' do describe ".before_validation" do
it 'calls build_tags' do it "calls build_tags" do
status = FactoryGirl.build(:status_message)
expect(status).to receive(:build_tags) expect(status).to receive(:build_tags)
status.save status.save
end end
end end
describe '.before_create' do describe ".before_create" do
it 'calls build_tags' do it "calls build_tags" do
status = FactoryGirl.build(:status_message)
expect(status).to receive(:build_tags) expect(status).to receive(:build_tags)
status.save status.save
end end
it 'calls filter_mentions' do it "calls filter_mentions" do
status = FactoryGirl.build(:status_message)
expect(status).to receive(:filter_mentions) expect(status).to receive(:filter_mentions)
status.save status.save
end end
end end
describe '.after_create' do describe ".after_create" do
it 'calls create_mentions' do it "calls create_mentions" do
status = FactoryGirl.build(:status_message, text: "text @{Test; #{alice.diaspora_handle}}") status = FactoryGirl.build(:status_message, text: "text @{Test; #{alice.diaspora_handle}}")
expect(status).to receive(:create_mentions).and_call_original expect(status).to receive(:create_mentions).and_call_original
status.save status.save
end end
end end
describe '#diaspora_handle=' do describe "#diaspora_handle=" do
it 'sets #author' do it "sets #author" do
person = FactoryGirl.create(:person) 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 post.diaspora_handle = person.diaspora_handle
expect(post.author).to eq(person) expect(post.author).to eq(person)
end end
@ -112,105 +107,104 @@ describe StatusMessage, :type => :model do
context "emptyness" do context "emptyness" do
it "needs either a message or at least one photo" do it "needs either a message or at least one photo" do
n = @user.build_post(:status_message, :text => nil) post = user.build_post(:status_message, text: nil)
expect(n).not_to be_valid expect(post).not_to be_valid
n.text = "" post.text = ""
expect(n).not_to be_valid expect(post).not_to be_valid
n.text = "wales" post.text = "wales"
expect(n).to be_valid expect(post).to be_valid
n.text = nil 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! photo.save!
n.photos << photo post.photos << photo
expect(n).to be_valid expect(post).to be_valid
expect(n.errors.full_messages).to eq([]) expect(post.errors.full_messages).to eq([])
end end
it "doesn't check for content when author is remote (federation...)" do it "doesn't check for content when author is remote (federation...)" do
p = FactoryGirl.build(:status_message, text: nil) post = FactoryGirl.build(:status_message, text: nil)
expect(p).to be_valid expect(post).to be_valid
end end
end end
it 'should be postable through the user' do it "should be postable through the user" do
message = "Users do things" 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) db_status = StatusMessage.find(status.id)
expect(db_status.text).to eq(message) expect(db_status.text).to eq(message)
end end
it 'should require status messages not be more than 65535 characters long' do it "should require status messages not be more than 65535 characters long" do
message = 'a' * (65535+1) message = "a" * (65_535 + 1)
status_message = FactoryGirl.build(:status_message, :text => message) status_message = FactoryGirl.build(:status_message, text: message)
expect(status_message).not_to be_valid expect(status_message).not_to be_valid
end end
describe 'mentions' do describe "mentions" do
before do let(:people) { [alice, bob, eve].map(&:person) }
@people = [alice, bob, eve].map{|u| u.person} let(:test_string) {
@test_string = <<-STR "@{Raphael; #{people[0].diaspora_handle}} can mention people like Raphael @{Ilya; #{people[1].diaspora_handle}}
@{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"
can mention people like Raphaellike Raphael @{Daniel; #{@people[2].diaspora_handle}} can mention people like Raph }
STR let(:status_message) { create(:status_message, text: test_string) }
@sm = FactoryGirl.create(:status_message, :text => @test_string )
end
describe '#create_mentions' do describe "#create_mentions" do
it 'creates a mention for everyone mentioned in the message' do it "creates a mention for everyone mentioned in the message" do
expect(Diaspora::Mentionable).to receive(:people_from_string).and_return(@people) status_message
@sm.mentions.delete_all expect(Diaspora::Mentionable).to receive(:people_from_string).and_return(people)
@sm.create_mentions status_message.mentions.delete_all
expect(@sm.mentions(true).map{|m| m.person}.to_set).to eq(@people.to_set) status_message.create_mentions
expect(status_message.mentions(true).map(&:person).to_set).to eq(people.to_set)
end end
it 'does not barf if it gets called twice' do it "does not barf if it gets called twice" do
@sm.create_mentions status_message.create_mentions
expect{ expect {
@sm.create_mentions status_message.create_mentions
}.to_not raise_error }.to_not raise_error
end end
end end
describe '#mentioned_people' do describe "#mentioned_people" do
it 'calls create_mentions if there are no mentions in the db' do it "calls create_mentions if there are no mentions in the db" do
@sm.mentions.delete_all status_message.mentions.delete_all
expect(@sm).to receive(:create_mentions) expect(status_message).to receive(:create_mentions)
@sm.mentioned_people status_message.mentioned_people
end end
it 'returns the mentioned people' do it "returns the mentioned people" do
@sm.mentions.delete_all status_message.mentions.delete_all
expect(@sm.mentioned_people.to_set).to eq(@people.to_set) expect(status_message.mentioned_people.to_set).to eq(people.to_set)
end end
it 'does not call create_mentions if there are mentions in the db' do it "does not call create_mentions if there are mentions in the db" do
expect(@sm).not_to receive(:create_mentions) expect(status_message).not_to receive(:create_mentions)
@sm.mentioned_people status_message.mentioned_people
end end
end end
describe "#mentions?" do describe "#mentions?" do
it 'returns true if the person was mentioned' do it "returns true if the person was mentioned" do
expect(@sm.mentions?(@people[0])).to be true expect(status_message.mentions?(people[0])).to be true
end end
it 'returns false if the person was not mentioned' do it "returns false if the person was not mentioned" do
expect(@sm.mentions?(FactoryGirl.build(:person))).to be false expect(status_message.mentions?(FactoryGirl.build(:person))).to be false
end end
end end
describe "#notify_person" do 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) expect(Notification).to receive(:notify).with(alice, anything, anything)
@sm.notify_person(alice.person) status_message.notify_person(alice.person)
end end
end end
describe "#filter_mentions" do 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 = FactoryGirl.build(:status_message_in_aspect)
msg_txt = msg.raw_message msg_txt = msg.raw_message
@ -233,222 +227,241 @@ STR
end end
describe "#nsfw" do describe "#nsfw" do
it 'returns MatchObject (true) if the post contains #nsfw (however capitalised)' do it "returns MatchObject (true) if the post contains #nsfw (however capitalised)" do
status = FactoryGirl.build(:status_message, :text => "This message is #nSFw") status = FactoryGirl.build(:status_message, text: "This message is #nSFw")
expect(status.nsfw).to be_truthy expect(status.nsfw).to be_truthy
end end
it 'returns nil (false) if the post does not contain #nsfw' do it "returns nil (false) if the post does not contain #nsfw" do
status = FactoryGirl.build(:status_message, :text => "This message is #sFW") status = FactoryGirl.build(:status_message, text: "This message is #sFW")
expect(status.nsfw).to be false expect(status.nsfw).to be false
end end
end end
describe 'tags' do describe "tags" do
before do before do
@object = FactoryGirl.build(:status_message) @object = FactoryGirl.build(:status_message)
end 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 assert_equal ActsAsTaggableOn.force_lowercase, true
msg_lc = FactoryGirl.build(:status_message, :text => '#newhere') msg_lc = FactoryGirl.build(:status_message, text: "#newhere")
msg_uc = FactoryGirl.build(:status_message, :text => '#NewHere') msg_uc = FactoryGirl.build(:status_message, text: "#NewHere")
msg_cp = 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 tag_array = msg_lc.tags
expect(msg_uc.tags).to match_array(tag_array) expect(msg_uc.tags).to match_array(tag_array)
expect(msg_cp.tags).to match_array(tag_array) expect(msg_cp.tags).to match_array(tag_array)
end end
it 'should require tag name not be more than 255 characters long' do it "should require tag name not be more than 255 characters long" do
message = "##{'a' * (255+1)}" message = "##{'a' * (255 + 1)}"
status_message = FactoryGirl.build(:status_message, :text => message) status_message = FactoryGirl.build(:status_message, text: message)
expect(status_message).not_to be_valid expect(status_message).not_to be_valid
end end
end end
describe "XML" do 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(:xml) { message.to_xml.to_s }
let(:marshalled) { StatusMessage.from_xml(xml) } 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>" text = "[url](http://example.org)<script> alert('xss should be federated');</script>"
message.text = text message.text = text
expect(xml).to include Builder::XChar.encode(text) expect(xml).to include Builder::XChar.encode(text)
end end
it 'serializes the message' do it "serializes the message" do
expect(xml).to include "<raw_message>I hate WALRUSES!</raw_message>" expect(xml).to include "<raw_message>I hate WALRUSES!</raw_message>"
end end
it 'serializes the author address' do it "serializes the author address" do
expect(xml).to include(@user.person.diaspora_handle) expect(xml).to include(user.person.diaspora_handle)
end end
describe '.from_xml' do describe ".from_xml" do
it 'marshals the message' do it "marshals the message" do
expect(marshalled.text).to eq("I hate WALRUSES!") expect(marshalled.text).to eq("I hate WALRUSES!")
end end
it 'marshals the guid' do it "marshals the guid" do
expect(marshalled.guid).to eq(message.guid) expect(marshalled.guid).to eq(message.guid)
end end
it 'marshals the author' do it "marshals the author" do
expect(marshalled.author).to eq(message.author) expect(marshalled.author).to eq(message.author)
end end
it 'marshals the diaspora_handle' do it "marshals the diaspora_handle" do
expect(marshalled.diaspora_handle).to eq(message.diaspora_handle) expect(marshalled.diaspora_handle).to eq(message.diaspora_handle)
end end
end end
context 'with some photos' do context "with some photos" do
before do before do
message.photos << FactoryGirl.build(:photo) message.photos << FactoryGirl.build(:photo)
message.photos << FactoryGirl.build(:photo) message.photos << FactoryGirl.build(:photo)
end end
it 'serializes the photos' do it "serializes the photos" do
expect(xml).to include "photo" expect(xml).to include "photo"
expect(xml).to include message.photos.first.remote_photo_path expect(xml).to include message.photos.first.remote_photo_path
end end
describe '.from_xml' do describe ".from_xml" do
it 'marshals the photos' do it "marshals the photos" do
expect(marshalled.photos.size).to eq(2) expect(marshalled.photos.size).to eq(2)
end end
it 'handles existing photos' do it "handles existing photos" do
message.photos.each(&:save!) message.photos.each(&:save!)
expect(marshalled).to be_valid expect(marshalled).to be_valid
end end
end end
end end
context 'with a location' do context "with a location" do
before do before do
message.location = FactoryGirl.build(:location) message.location = FactoryGirl.build(:location)
end end
it 'serializes the location' do it "serializes the location" do
expect(xml).to include "location" expect(xml).to include "location"
expect(xml).to include "lat" expect(xml).to include "lat"
expect(xml).to include "lng" expect(xml).to include "lng"
end end
describe ".from_xml" do describe ".from_xml" do
it 'marshals the location' do it "marshals the location" do
expect(marshalled.location).to be_present expect(marshalled.location).to be_present
end end
end end
end end
context 'with a poll' do context "with a poll" do
before do before do
message.poll = FactoryGirl.build(:poll) message.poll = FactoryGirl.build(:poll)
end end
it 'serializes the poll' do it "serializes the poll" do
expect(xml).to include "poll" expect(xml).to include "poll"
expect(xml).to include "question" expect(xml).to include "question"
expect(xml).to include "poll_answer" expect(xml).to include "poll_answer"
end end
describe ".from_xml" do describe ".from_xml" do
it 'marshals the poll' do it "marshals the poll" do
expect(marshalled.poll).to be_present expect(marshalled.poll).to be_present
end end
it 'marshals the poll answers' do it "marshals the poll answers" do
expect(marshalled.poll.poll_answers.size).to eq(2) expect(marshalled.poll.poll_answers.size).to eq(2)
end end
end end
end end
end end
describe '#after_dispatch' do describe "#after_dispatch" do
before do before do
@photos = [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))] alice.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name))]
@photos.each(&:save!) @photos.each(&:save!)
@status_message = alice.build_post(:status_message, text: "the best pebble.")
@status_message = alice.build_post(:status_message, :text => "the best pebble.") @status_message.photos << @photos
@status_message.photos << @photos
@status_message.save! @status_message.save!
alice.add_to_streams(@status_message, alice.aspects) alice.add_to_streams(@status_message, alice.aspects)
end 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) @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 end
it 'dispatches any attached photos' do
it "dispatches any attached photos" do
expect(alice).to receive(:dispatch_post).twice expect(alice).to receive(:dispatch_post).twice
@status_message.after_dispatch(alice) @status_message.after_dispatch(alice)
end end
end end
describe 'oembed' do describe "oembed" do
before do let(:youtube_url) { "https://www.youtube.com/watch?v=3PtFwlKfvHI" }
@youtube_url = "https://www.youtube.com/watch?v=3PtFwlKfvHI" let(:message_text) { "#{youtube_url} is so cool. so is this link -> https://joindiaspora.com" }
@message_text = "#{@youtube_url} is so cool. so is this link -> https://joindiaspora.com" let(:status_message) { FactoryGirl.build(:status_message, text: message_text) }
end
it 'should queue a GatherOembedData if it includes a link' do it "should queue a GatherOembedData if it includes a link" do
sm = FactoryGirl.build(:status_message, :text => @message_text) status_message
expect(Workers::GatherOEmbedData).to receive(:perform_async).with(instance_of(Fixnum), instance_of(String)) expect(Workers::GatherOEmbedData).to receive(:perform_async).with(instance_of(Fixnum), instance_of(String))
sm.save status_message.save
end end
describe '#contains_oembed_url_in_text?' do describe "#contains_oembed_url_in_text?" do
it 'returns the oembed urls found in the raw message' do it "returns the oembed urls found in the raw message" do
sm = FactoryGirl.build(:status_message, :text => @message_text) expect(status_message.contains_oembed_url_in_text?).not_to be_nil
expect(sm.contains_oembed_url_in_text?).not_to be_nil expect(status_message.oembed_url).to eq(youtube_url)
expect(sm.oembed_url).to eq(@youtube_url)
end end
end end
end end
describe 'opengraph' do describe "opengraph" do
before do let(:ninegag_url) { "http://9gag.com/gag/a1AMW16" }
@ninegag_url = "http://9gag.com/gag/a1AMW16" let(:youtube_url) { "https://www.youtube.com/watch?v=3PtFwlKfvHI" }
@youtube_url = "https://www.youtube.com/watch?v=3PtFwlKfvHI" let(:message_text) { "#{ninegag_url} is so cool. so is this link -> https://joindiaspora.com" }
@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" }
@oemessage_text = "#{@youtube_url} is so cool. so is this link -> https://joindiaspora.com" let(:status_message) { build(:status_message, text: message_text) }
end
it 'should queue a GatherOpenGraphData if it includes a link' do it "should queue a GatherOpenGraphData if it includes a link" do
sm = FactoryGirl.build(:status_message, :text => @message_text) status_message
expect(Workers::GatherOpenGraphData).to receive(:perform_async).with(instance_of(Fixnum), instance_of(String)) expect(Workers::GatherOpenGraphData).to receive(:perform_async).with(instance_of(Fixnum), instance_of(String))
sm.save status_message.save
end end
describe '#contains_open_graph_url_in_text?' do describe "#contains_open_graph_url_in_text?" do
it 'returns the opengraph urls found in the raw message' do it "returns the opengraph urls found in the raw message" do
sm = FactoryGirl.build(:status_message, :text => @message_text) expect(status_message.contains_open_graph_url_in_text?).not_to be_nil
expect(sm.contains_open_graph_url_in_text?).not_to be_nil expect(status_message.open_graph_url).to eq(ninegag_url)
expect(sm.open_graph_url).to eq(@ninegag_url)
end end
it 'returns nil if the link is from trusted oembed provider' do it "returns nil if the link is from trusted oembed provider" do
sm = FactoryGirl.build(:status_message, :text => @oemessage_text) status_message = FactoryGirl.build(:status_message, text: oemessage_text)
expect(sm.contains_open_graph_url_in_text?).to be_nil expect(status_message.contains_open_graph_url_in_text?).to be_nil
expect(sm.open_graph_url).to be_nil expect(status_message.open_graph_url).to be_nil
end end
end end
end end
describe "validation" do describe "validation" do
let(:status_message) { build(:status_message, text: @message_text) }
it "should not be valid if the author is missing" do it "should not be valid if the author is missing" do
sm = FactoryGirl.build(:status_message, text: @message_text) status_message.author = nil
sm.author = nil expect(status_message).not_to be_valid
expect(sm).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 end
end end