Merge branch 'next-minor' into develop

This commit is contained in:
Benjamin Neff 2016-11-22 03:55:12 +01:00
commit 99b7229d5a
20 changed files with 147 additions and 25 deletions

View file

@ -16,12 +16,15 @@
* Use typeahead.js from rails-assets.org [#7192](https://github.com/diaspora/diaspora/pull/7192) * Use typeahead.js from rails-assets.org [#7192](https://github.com/diaspora/diaspora/pull/7192)
* Refactor ShareVisibilitesController to use PostService [#7196](https://github.com/diaspora/diaspora/pull/7196) * Refactor ShareVisibilitesController to use PostService [#7196](https://github.com/diaspora/diaspora/pull/7196)
* Unify desktop and mobile head elements [#7194](https://github.com/diaspora/diaspora/pull/7194) * Unify desktop and mobile head elements [#7194](https://github.com/diaspora/diaspora/pull/7194)
* Refactor flash messages on ajax errors for comments, likes, reshares and aspect memberships [#7202](https://github.com/diaspora/diaspora/pull/7202)
## Bug fixes ## Bug fixes
* Fix fetching comments after fetching likes [#7167](https://github.com/diaspora/diaspora/pull/7167) * Fix fetching comments after fetching likes [#7167](https://github.com/diaspora/diaspora/pull/7167)
* Hide 'reshare' button on already reshared posts [#7169](https://github.com/diaspora/diaspora/pull/7169) * Hide 'reshare' button on already reshared posts [#7169](https://github.com/diaspora/diaspora/pull/7169)
* Only reload profile header when changing aspect memberships [#7183](https://github.com/diaspora/diaspora/pull/7183) * Only reload profile header when changing aspect memberships [#7183](https://github.com/diaspora/diaspora/pull/7183)
* Fix visiblity on invitation modal when opening it from the stream [#7191](https://github.com/diaspora/diaspora/pull/7191) * Fix visiblity on invitation modal when opening it from the stream [#7191](https://github.com/diaspora/diaspora/pull/7191)
* Add avatar fallback on tags page [#7198](https://github.com/diaspora/diaspora/pull/7198)
* Update notifications when changing the stream [#7199](https://github.com/diaspora/diaspora/pull/7199)
## Features ## Features
* Show spinner when loading comments in the stream [#7170](https://github.com/diaspora/diaspora/pull/7170) * Show spinner when loading comments in the stream [#7170](https://github.com/diaspora/diaspora/pull/7170)

View file

@ -115,6 +115,7 @@ var app = {
// so we use Backbone.history.navigate instead. // so we use Backbone.history.navigate instead.
var change = Backbone.history.navigate(link.attr("href").substring(1) ,true); var change = Backbone.history.navigate(link.attr("href").substring(1) ,true);
if(change === undefined) { Backbone.history.loadUrl(link.attr("href").substring(1)); } if(change === undefined) { Backbone.history.loadUrl(link.attr("href").substring(1)); }
app.notificationsCollection.fetch();
}); });
}, },

View file

@ -72,8 +72,8 @@ app.models.Post.Interactions = Backbone.Model.extend({
self.set({"likes_count" : self.get("likes_count") + 1}); self.set({"likes_count" : self.get("likes_count") + 1});
self.likes.trigger("change"); self.likes.trigger("change");
}, },
error: function() { error: function(model, response) {
app.flashMessages.error(Diaspora.I18n.t("failed_to_like")); app.flashMessages.handleAjaxError(response);
} }
}); });
@ -95,8 +95,8 @@ app.models.Post.Interactions = Backbone.Model.extend({
var self = this; var self = this;
options = options || {}; options = options || {};
this.comments.make(text).fail(function () { this.comments.make(text).fail(function(response) {
app.flashMessages.error(Diaspora.I18n.t("failed_to_comment")); app.flashMessages.handleAjaxError(response);
if (options.error) { options.error(); } if (options.error) { options.error(); }
}).done(function() { }).done(function() {
self.post.set({participation: true}); self.post.set({participation: true});
@ -123,8 +123,8 @@ app.models.Post.Interactions = Backbone.Model.extend({
interactions.set({"reshares_count": interactions.get("reshares_count") + 1}); interactions.set({"reshares_count": interactions.get("reshares_count") + 1});
interactions.reshares.trigger("change"); interactions.reshares.trigger("change");
}) })
.fail(function(){ .fail(function(response) {
app.flashMessages.error(Diaspora.I18n.t("reshares.duplicate")); app.flashMessages.handleAjaxError(response);
}); });
app.instrument("track", "Reshare"); app.instrument("track", "Reshare");

View file

@ -125,7 +125,7 @@ app.views.AspectMembership = app.views.Base.extend({
_displayError: function(model, resp) { _displayError: function(model, resp) {
this._done(); this._done();
this.dropdown.closest(".aspect_membership_dropdown").removeClass("open"); // close the dropdown this.dropdown.closest(".aspect_membership_dropdown").removeClass("open"); // close the dropdown
app.flashMessages.error(resp.responseText); app.flashMessages.handleAjaxError(resp);
}, },
// remove the membership with the given id // remove the membership with the given id
@ -134,7 +134,7 @@ app.views.AspectMembership = app.views.Base.extend({
this.listenToOnce(membership, "sync", this._successDestroyCb); this.listenToOnce(membership, "sync", this._successDestroyCb);
this.listenToOnce(membership, "error", this._displayError); this.listenToOnce(membership, "error", this._displayError);
return membership.destroy(); return membership.destroy({wait: true});
}, },
_successDestroyCb: function(aspectMembership) { _successDestroyCb: function(aspectMembership) {

View file

@ -16,5 +16,13 @@ app.views.FlashMessages = app.views.Base.extend({
error: function(message){ error: function(message){
this._flash(message, true); this._flash(message, true);
},
handleAjaxError: function(response) {
if (response.status === 0) {
this.error(Diaspora.I18n.t("errors.connection"));
} else {
this.error(response.responseText);
}
} }
}); });

View file

@ -5,6 +5,8 @@ app.views.Tags = Backbone.View.extend({
if(app.publisher) { if(app.publisher) {
app.publisher.setText("#"+ opts.hashtagName + " "); app.publisher.setText("#"+ opts.hashtagName + " ");
} }
// add avatar fallback if it can't be loaded
$(app.views.Base.prototype.avatars.selector).error(app.views.Base.prototype.avatars.fallback);
} }
}); });
// @license-end // @license-end

View file

@ -98,8 +98,12 @@
success: function() { success: function() {
Diaspora.Mobile.PostActions.toggleActive(link); Diaspora.Mobile.PostActions.toggleActive(link);
}, },
error: function() { error: function(response) {
alert(Diaspora.I18n.t("failed_to_reshare")); if (response.status === 0) {
alert(Diaspora.I18n.t("errors.connection"));
} else {
alert(response.responseText);
}
}, },
complete: function() { complete: function() {
Diaspora.Mobile.PostActions.hideLoader(link); Diaspora.Mobile.PostActions.hideLoader(link);

View file

@ -12,11 +12,17 @@ class CommentsController < ApplicationController
end end
def create def create
comment = comment_service.create(params[:post_id], params[:text]) begin
comment = comment_service.create(params[:post_id], params[:text])
rescue ActiveRecord::RecordNotFound
render text: I18n.t("comments.create.error"), status: 404
return
end
if comment if comment
respond_create_success(comment) respond_create_success(comment)
else else
render nothing: true, status: 404 render text: I18n.t("comments.create.error"), status: 422
end end
end end

View file

@ -26,7 +26,7 @@ class LikesController < ApplicationController
format.json { render :json => @like.as_api_response(:backbone), :status => 201 } format.json { render :json => @like.as_api_response(:backbone), :status => 201 }
end end
else else
render :nothing => true, :status => 422 render text: I18n.t("likes.create.error"), status: 422
end end
end end

View file

@ -14,7 +14,7 @@ class ResharesController < ApplicationController
current_user.dispatch_post(@reshare) current_user.dispatch_post(@reshare)
render :json => ExtremePostPresenter.new(@reshare, current_user), :status => 201 render :json => ExtremePostPresenter.new(@reshare, current_user), :status => 201
else else
render :nothing => true, :status => 422 render text: I18n.t("reshares.create.error"), status: 422
end end
end end

View file

@ -240,6 +240,8 @@ en:
explanation: "Post to diaspora* from anywhere by bookmarking this link => %{link}." explanation: "Post to diaspora* from anywhere by bookmarking this link => %{link}."
comments: comments:
create:
error: "Failed to comment."
new_comment: new_comment:
comment: "Comment" comment: "Comment"
commenting: "Commenting..." commenting: "Commenting..."
@ -591,6 +593,10 @@ en:
source_package: "Download the source code package" source_package: "Download the source code package"
be_excellent: "Be excellent to each other! ♥" be_excellent: "Be excellent to each other! ♥"
likes:
create:
error: "Failed to like."
notifications: notifications:
started_sharing: started_sharing:
zero: "%{actors} started sharing with you." zero: "%{actors} started sharing with you."
@ -991,6 +997,8 @@ en:
invalid_invite: "The invite link you provided is no longer valid!" invalid_invite: "The invite link you provided is no longer valid!"
reshares: reshares:
create:
error: "Failed to reshare."
reshare: reshare:
reshared_via: "Reshared via" reshared_via: "Reshared via"
reshare_confirmation: "Reshare %{author}s post?" reshare_confirmation: "Reshare %{author}s post?"

View file

@ -87,6 +87,9 @@ en:
success: "Your new aspect <%= name %> was created" success: "Your new aspect <%= name %> was created"
failure: "Aspect creation failed." failure: "Aspect creation failed."
errors:
connection: "Unable to connect to the server."
timeago: timeago:
prefixAgo: "" prefixAgo: ""
prefixFromNow: "" prefixFromNow: ""
@ -186,9 +189,6 @@ en:
one: "In <%= count %> aspect" one: "In <%= count %> aspect"
other: "In <%= count %> aspects" other: "In <%= count %> aspects"
show_more: "Show more" show_more: "Show more"
failed_to_like: "Failed to like. Maybe the author is ignoring you?"
failed_to_reshare: "Failed to reshare!"
failed_to_comment: "Failed to comment. Maybe the author is ignoring you?"
failed_to_post_message: "Failed to post message!" failed_to_post_message: "Failed to post message!"
failed_to_remove: "Failed to remove the entry!" failed_to_remove: "Failed to remove the entry!"
comments: comments:
@ -196,7 +196,6 @@ en:
hide: "Hide comments" hide: "Hide comments"
no_comments: "There are no comments yet." no_comments: "There are no comments yet."
reshares: reshares:
duplicate: "That good, eh? Youve already reshared that post!"
successful: "The post was successfully reshared!" successful: "The post was successfully reshared!"
post: "Reshare <%= name %>s post?" post: "Reshare <%= name %>s post?"
aspect_navigation: aspect_navigation:

View file

@ -67,6 +67,7 @@ describe CommentsController, :type => :controller do
expect(alice).not_to receive(:comment) expect(alice).not_to receive(:comment)
post :create, comment_hash post :create, comment_hash
expect(response.code).to eq("404") expect(response.code).to eq("404")
expect(response.body).to eq(I18n.t("comments.create.error"))
end end
end end

View file

@ -46,7 +46,7 @@ describe ResharesController, :type => :controller do
it 'doesn\'t allow the user to reshare the post again' do it 'doesn\'t allow the user to reshare the post again' do
post_request! post_request!
expect(response.code).to eq('422') expect(response.code).to eq('422')
expect(response.body.strip).to be_empty expect(response.body).to eq(I18n.t("reshares.create.error"))
end end
end end

View file

@ -96,4 +96,39 @@ describe("app", function() {
expect(app._changeLocation).toHaveBeenCalledWith("/users/sign_in"); expect(app._changeLocation).toHaveBeenCalledWith("/users/sign_in");
}); });
}); });
describe("setupBackboneLinks", function() {
it("calls Backbone.history.start", function() {
spyOn(Backbone.history, "start");
app.setupBackboneLinks();
expect(Backbone.history.start).toHaveBeenCalledWith({pushState: true});
});
context("when clicking a backbone link", function() {
beforeEach(function() {
app.stream = {basePath: function() { return "/stream"; }};
app.notificationsCollection = {fetch: $.noop};
spyOn(Backbone.history, "start");
this.link = $("<a href='/backbone-link' rel='backbone'>");
spec.content().append(this.link);
app.setupBackboneLinks();
});
afterEach(function() {
app.stream = undefined;
});
it("calls Backbone.history.navigate", function() {
spyOn(Backbone.history, "navigate");
this.link.click();
expect(Backbone.history.navigate).toHaveBeenCalledWith("backbone-link", true);
});
it("fetches new notifications", function() {
spyOn(app.notificationsCollection, "fetch");
this.link.click();
expect(app.notificationsCollection.fetch).toHaveBeenCalled();
});
});
});
}); });

View file

@ -6,6 +6,8 @@ describe("app.models.Post.Interactions", function(){
this.interactions = this.post.interactions; this.interactions = this.post.interactions;
this.author = factory.author({guid: "loggedInAsARockstar"}); this.author = factory.author({guid: "loggedInAsARockstar"});
loginAs({guid: "loggedInAsARockstar"}); loginAs({guid: "loggedInAsARockstar"});
spec.content().append($("<div id='flash-container'>"));
app.flashMessages = new app.views.FlashMessages({el: spec.content().find("#flash-container")});
this.userLike = new app.models.Like({author : this.author}); this.userLike = new app.models.Like({author : this.author});
}); });
@ -47,6 +49,16 @@ describe("app.models.Post.Interactions", function(){
jasmine.Ajax.requests.mostRecent().respondWith(ajaxSuccess); jasmine.Ajax.requests.mostRecent().respondWith(ajaxSuccess);
expect(this.interactions.likes.trigger).toHaveBeenCalledWith("change"); expect(this.interactions.likes.trigger).toHaveBeenCalledWith("change");
}); });
it("displays a flash message on errors", function() {
spyOn(app.flashMessages, "handleAjaxError").and.callThrough();
this.interactions.like();
jasmine.Ajax.requests.mostRecent().respondWith({status: 400, responseText: "error message"});
expect(app.flashMessages.handleAjaxError).toHaveBeenCalled();
expect(app.flashMessages.handleAjaxError.calls.argsFor(0)[0].responseText).toBe("error message");
expect(spec.content().find(".flash-message")).toBeErrorFlashMessage("error message");
});
}); });
describe("unlike", function(){ describe("unlike", function(){
@ -110,6 +122,16 @@ describe("app.models.Post.Interactions", function(){
jasmine.Ajax.requests.mostRecent().respondWith(ajaxSuccess); jasmine.Ajax.requests.mostRecent().respondWith(ajaxSuccess);
expect(this.post.get("participation")).toBeTruthy(); expect(this.post.get("participation")).toBeTruthy();
}); });
it("displays a flash message on errors", function() {
spyOn(app.flashMessages, "handleAjaxError").and.callThrough();
this.interactions.reshare();
jasmine.Ajax.requests.mostRecent().respondWith({status: 400, responseText: "error message"});
expect(app.flashMessages.handleAjaxError).toHaveBeenCalled();
expect(app.flashMessages.handleAjaxError.calls.argsFor(0)[0].responseText).toBe("error message");
expect(spec.content().find(".flash-message")).toBeErrorFlashMessage("error message");
});
}); });
describe("userLike", function(){ describe("userLike", function(){
@ -264,6 +286,16 @@ describe("app.models.Post.Interactions", function(){
jasmine.Ajax.requests.mostRecent().respondWith({status: 400}); jasmine.Ajax.requests.mostRecent().respondWith({status: 400});
expect(error).toHaveBeenCalled(); expect(error).toHaveBeenCalled();
}); });
it("displays a flash message", function() {
spyOn(app.flashMessages, "handleAjaxError").and.callThrough();
this.interactions.comment("text");
jasmine.Ajax.requests.mostRecent().respondWith({status: 400, responseText: "error message"});
expect(app.flashMessages.handleAjaxError).toHaveBeenCalled();
expect(app.flashMessages.handleAjaxError.calls.argsFor(0)[0].responseText).toBe("error message");
expect(spec.content().find(".flash-message")).toBeErrorFlashMessage("error message");
});
}); });
}); });
}); });

View file

@ -55,9 +55,12 @@ describe("app.views.AspectMembership", function(){
}); });
it('displays an error when it fails', function() { it('displays an error when it fails', function() {
spyOn(app.flashMessages, "handleAjaxError").and.callThrough();
this.newAspect.trigger('click'); this.newAspect.trigger('click');
jasmine.Ajax.requests.mostRecent().respondWith(resp_fail); jasmine.Ajax.requests.mostRecent().respondWith(resp_fail);
expect(app.flashMessages.handleAjaxError).toHaveBeenCalled();
expect(app.flashMessages.handleAjaxError.calls.argsFor(0)[0].responseText).toBe("error message");
expect(spec.content().find(".flash-message")).toBeErrorFlashMessage("error message"); expect(spec.content().find(".flash-message")).toBeErrorFlashMessage("error message");
}); });
}); });
@ -95,9 +98,12 @@ describe("app.views.AspectMembership", function(){
}); });
it('displays an error when it fails', function() { it('displays an error when it fails', function() {
spyOn(app.flashMessages, "handleAjaxError").and.callThrough();
this.oldAspect.trigger('click'); this.oldAspect.trigger('click');
jasmine.Ajax.requests.mostRecent().respondWith(resp_fail); jasmine.Ajax.requests.mostRecent().respondWith(resp_fail);
expect(app.flashMessages.handleAjaxError).toHaveBeenCalled();
expect(app.flashMessages.handleAjaxError.calls.argsFor(0)[0].responseText).toBe("error message");
expect(spec.content().find(".flash-message")).toBeErrorFlashMessage("error message"); expect(spec.content().find(".flash-message")).toBeErrorFlashMessage("error message");
}); });
}); });

View file

@ -116,9 +116,6 @@ describe("app.views.CommentStream", function(){
it("doesn't add the comment to the view", function() { it("doesn't add the comment to the view", function() {
this.request.respondWith({status: 500}); this.request.respondWith({status: 500});
expect(this.view.$(".comment-content p").text()).not.toEqual("a new comment"); expect(this.view.$(".comment-content p").text()).not.toEqual("a new comment");
expect(this.view.$(".flash-message")).toBeErrorFlashMessage(
"Failed to comment. Maybe the author is ignoring you?"
);
}); });
it("doesn't reset the comment box value", function() { it("doesn't reset the comment box value", function() {

View file

@ -30,4 +30,18 @@ describe("app.views.FlashMessages", function(){
expect($(".flash-message").text().trim()).toBe("error!"); expect($(".flash-message").text().trim()).toBe("error!");
}); });
}); });
describe("handleAjaxError", function() {
it("shows a generic error if the connection failed", function() {
spyOn(flashMessages, "error");
flashMessages.handleAjaxError({status: 0});
expect(flashMessages.error).toHaveBeenCalledWith(Diaspora.I18n.t("errors.connection"));
});
it("shows the error given in the responseText otherwise", function() {
spyOn(flashMessages, "error");
flashMessages.handleAjaxError({status: 400, responseText: "some specific ajax error"});
expect(flashMessages.error).toHaveBeenCalledWith("some specific ajax error");
});
});
}); });

View file

@ -223,10 +223,16 @@ describe("Diaspora.Mobile.PostActions", function(){
expect(Diaspora.Mobile.PostActions.toggleActive).toHaveBeenCalledWith(this.reshareLink); expect(Diaspora.Mobile.PostActions.toggleActive).toHaveBeenCalledWith(this.reshareLink);
}); });
it("pops an alert on error", function(){ it("pops an alert on server errors", function() {
this.reshareLink.click(); this.reshareLink.click();
jasmine.Ajax.requests.mostRecent().respondWith({status: 400}); jasmine.Ajax.requests.mostRecent().respondWith({status: 400, responseText: "reshare failed"});
expect(window.alert).toHaveBeenCalledWith(Diaspora.I18n.t("failed_to_reshare")); expect(window.alert).toHaveBeenCalledWith("reshare failed");
});
it("pops an alert on network errors", function() {
this.reshareLink.click();
jasmine.Ajax.requests.mostRecent().abort();
expect(window.alert).toHaveBeenCalledWith(Diaspora.I18n.t("errors.connection"));
}); });
}); });
}); });