Add collection to app.views.NotificationDropdown and app.views.Notifications
closes #6952
This commit is contained in:
parent
9b72527f3e
commit
af331bfb30
20 changed files with 875 additions and 304 deletions
|
|
@ -19,6 +19,8 @@
|
|||
* Add a dark color theme [#7152](https://github.com/diaspora/diaspora/pull/7152)
|
||||
* Added setting for custom changelog URL [#7166](https://github.com/diaspora/diaspora/pull/7166)
|
||||
* Show more information of recipients on conversation creation [#7129](https://github.com/diaspora/diaspora/pull/7129)
|
||||
* Update notifications every 5 minutes and when opening the notification dropdown [#6952](https://github.com/diaspora/diaspora/pull/6952)
|
||||
* Show browser notifications when receiving new unread notifications [#6952](https://github.com/diaspora/diaspora/pull/6952)
|
||||
|
||||
# 0.6.1.0
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ var app = {
|
|||
|
||||
setupHeader: function() {
|
||||
if(app.currentUser.authenticated()) {
|
||||
app.notificationsCollection = new app.collections.Notifications();
|
||||
app.header = new app.views.Header();
|
||||
$("header").prepend(app.header.el);
|
||||
app.header.render();
|
||||
|
|
|
|||
118
app/assets/javascripts/app/collections/notifications.js
Normal file
118
app/assets/javascripts/app/collections/notifications.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
app.collections.Notifications = Backbone.Collection.extend({
|
||||
model: app.models.Notification,
|
||||
// URL parameter
|
||||
/* eslint-disable camelcase */
|
||||
url: Routes.notifications({per_page: 10, page: 1}),
|
||||
/* eslint-enable camelcase */
|
||||
page: 2,
|
||||
perPage: 5,
|
||||
unreadCount: 0,
|
||||
unreadCountByType: {},
|
||||
timeout: 300000, // 5 minutes
|
||||
|
||||
initialize: function() {
|
||||
this.pollNotifications();
|
||||
|
||||
setTimeout(function() {
|
||||
setInterval(this.pollNotifications.bind(this), this.timeout);
|
||||
}.bind(this), this.timeout);
|
||||
|
||||
Diaspora.BrowserNotification.requestPermission();
|
||||
},
|
||||
|
||||
pollNotifications: function() {
|
||||
var unreadCountBefore = this.unreadCount;
|
||||
this.fetch();
|
||||
|
||||
this.once("finishedLoading", function() {
|
||||
if (unreadCountBefore < this.unreadCount) {
|
||||
Diaspora.BrowserNotification.spawnNotification(
|
||||
Diaspora.I18n.t("notifications.new_notifications", {count: this.unreadCount}));
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
fetch: function(options) {
|
||||
options = options || {};
|
||||
options.remove = false;
|
||||
options.merge = true;
|
||||
options.parse = true;
|
||||
Backbone.Collection.prototype.fetch.apply(this, [options]);
|
||||
},
|
||||
|
||||
fetchMore: function() {
|
||||
var hasMoreNotifications = (this.page * this.perPage) <= this.length;
|
||||
// There are more notifications to load on the current page
|
||||
if (hasMoreNotifications) {
|
||||
this.page++;
|
||||
// URL parameter
|
||||
/* eslint-disable camelcase */
|
||||
var route = Routes.notifications({per_page: this.perPage, page: this.page});
|
||||
/* eslint-enable camelcase */
|
||||
this.fetch({url: route, pushBack: true});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds new models to the collection at the end or at the beginning of the collection and
|
||||
* then fires an event for each model of the collection. It will fire a different event
|
||||
* based on whether the models were added at the end (typically when the scroll triggers to load more
|
||||
* notifications) or at the beginning (new notifications have been added to the front of the list).
|
||||
*/
|
||||
set: function(items, options) {
|
||||
options = options || {};
|
||||
options.at = options.pushBack ? this.length : 0;
|
||||
|
||||
// Retreive back the new created models
|
||||
var models = [];
|
||||
var accu = function(model) { models.push(model); };
|
||||
this.on("add", accu);
|
||||
Backbone.Collection.prototype.set.apply(this, [items, options]);
|
||||
this.off("add", accu);
|
||||
|
||||
if (options.pushBack) {
|
||||
models.forEach(function(model) { this.trigger("pushBack", model); }.bind(this));
|
||||
} else {
|
||||
// Fires events in the reverse order so that the first event is prepended in first position
|
||||
models.reverse();
|
||||
models.forEach(function(model) { this.trigger("pushFront", model); }.bind(this));
|
||||
}
|
||||
this.trigger("finishedLoading");
|
||||
},
|
||||
|
||||
parse: function(response) {
|
||||
this.unreadCount = response.unread_count;
|
||||
this.unreadCountByType = response.unread_count_by_type;
|
||||
|
||||
return _.map(response.notification_list, function(item) {
|
||||
/* eslint-disable new-cap */
|
||||
var model = new this.model(item);
|
||||
/* eslint-enable new-cap */
|
||||
model.on("change:unread", this.onChangedUnreadStatus.bind(this));
|
||||
return model;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
setAllRead: function() {
|
||||
this.forEach(function(model) { model.setRead(); });
|
||||
},
|
||||
|
||||
setRead: function(guid) {
|
||||
this.find(function(model) { return model.guid === guid; }).setRead();
|
||||
},
|
||||
|
||||
setUnread: function(guid) {
|
||||
this.find(function(model) { return model.guid === guid; }).setUnread();
|
||||
},
|
||||
|
||||
onChangedUnreadStatus: function(model) {
|
||||
if (model.get("unread") === true) {
|
||||
this.unreadCount++;
|
||||
this.unreadCountByType[model.get("type")]++;
|
||||
} else {
|
||||
this.unreadCount = Math.max(this.unreadCount - 1, 0);
|
||||
this.unreadCountByType[model.get("type")] = Math.max(this.unreadCountByType[model.get("type")] - 1, 0);
|
||||
}
|
||||
this.trigger("update");
|
||||
}
|
||||
});
|
||||
69
app/assets/javascripts/app/models/notification.js
Normal file
69
app/assets/javascripts/app/models/notification.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
app.models.Notification = Backbone.Model.extend({
|
||||
constructor: function(attributes, options) {
|
||||
options = options || {};
|
||||
options.parse = true;
|
||||
Backbone.Model.apply(this, [attributes, options]);
|
||||
this.guid = this.get("id");
|
||||
},
|
||||
|
||||
/**
|
||||
* Flattens the notification object returned by the server.
|
||||
*
|
||||
* The server returns an object that looks like:
|
||||
*
|
||||
* {
|
||||
* "reshared": {
|
||||
* "id": 45,
|
||||
* "target_type": "Post",
|
||||
* "target_id": 11,
|
||||
* "recipient_id": 1,
|
||||
* "unread": true,
|
||||
* "created_at": "2015-10-27T19:56:30.000Z",
|
||||
* "updated_at": "2015-10-27T19:56:30.000Z",
|
||||
* "note_html": <html/>
|
||||
* },
|
||||
* "type": "reshared"
|
||||
* }
|
||||
*
|
||||
* The returned object looks like:
|
||||
*
|
||||
* {
|
||||
* "type": "reshared",
|
||||
* "id": 45,
|
||||
* "target_type": "Post",
|
||||
* "target_id": 11,
|
||||
* "recipient_id": 1,
|
||||
* "unread": true,
|
||||
* "created_at": "2015-10-27T19:56:30.000Z",
|
||||
* "updated_at": "2015-10-27T19:56:30.000Z",
|
||||
* "note_html": <html/>,
|
||||
* }
|
||||
*/
|
||||
parse: function(response) {
|
||||
var result = {type: response.type};
|
||||
result = $.extend(result, response[result.type]);
|
||||
return result;
|
||||
},
|
||||
|
||||
setRead: function() {
|
||||
this.setUnreadStatus(false);
|
||||
},
|
||||
|
||||
setUnread: function() {
|
||||
this.setUnreadStatus(true);
|
||||
},
|
||||
|
||||
setUnreadStatus: function(state) {
|
||||
if (this.get("unread") !== state) {
|
||||
$.ajax({
|
||||
url: Routes.notification(this.guid),
|
||||
/* eslint-disable camelcase */
|
||||
data: {set_unread: state},
|
||||
/* eslint-enable camelcase */
|
||||
type: "PUT",
|
||||
context: this,
|
||||
success: function() { this.set("unread", state); }
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -139,7 +139,7 @@ app.Router = Backbone.Router.extend({
|
|||
notifications: function() {
|
||||
this._loadContacts();
|
||||
this.renderAspectMembershipDropdowns($(document));
|
||||
new app.views.Notifications({el: "#notifications_container"});
|
||||
new app.views.Notifications({el: "#notifications_container", collection: app.notificationsCollection});
|
||||
},
|
||||
|
||||
peopleSearch: function() {
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ app.views.Header = app.views.Base.extend({
|
|||
},
|
||||
|
||||
postRenderTemplate: function() {
|
||||
new app.views.Notifications({ el: "#notification-dropdown" });
|
||||
this.notificationDropdown = new app.views.NotificationDropdown({ el: "#notification-dropdown" });
|
||||
new app.views.Notifications({el: "#notification-dropdown", collection: app.notificationsCollection});
|
||||
new app.views.NotificationDropdown({el: "#notification-dropdown", collection: app.notificationsCollection});
|
||||
new app.views.Search({el: "#header-search-form"});
|
||||
},
|
||||
|
||||
menuElement: function(){ return this.$("ul.dropdown"); },
|
||||
menuElement: function() { return this.$("ul.dropdown"); }
|
||||
});
|
||||
// @license-end
|
||||
|
|
|
|||
|
|
@ -6,16 +6,21 @@ app.views.NotificationDropdown = app.views.Base.extend({
|
|||
},
|
||||
|
||||
initialize: function(){
|
||||
$(document.body).click($.proxy(this.hideDropdown, this));
|
||||
$(document.body).click(this.hideDropdown.bind(this));
|
||||
|
||||
this.notifications = [];
|
||||
this.perPage = 5;
|
||||
this.hasMoreNotifs = true;
|
||||
this.badge = this.$el;
|
||||
this.dropdown = $("#notification-dropdown");
|
||||
this.dropdownNotifications = this.dropdown.find(".notifications");
|
||||
this.ajaxLoader = this.dropdown.find(".ajax-loader");
|
||||
this.perfectScrollbarInitialized = false;
|
||||
this.dropdownNotifications.scroll(this.dropdownScroll.bind(this));
|
||||
this.bindCollectionEvents();
|
||||
},
|
||||
|
||||
bindCollectionEvents: function() {
|
||||
this.collection.on("pushFront", this.onPushFront.bind(this));
|
||||
this.collection.on("pushBack", this.onPushBack.bind(this));
|
||||
this.collection.on("finishedLoading", this.finishLoading.bind(this));
|
||||
},
|
||||
|
||||
toggleDropdown: function(evt){
|
||||
|
|
@ -31,12 +36,11 @@ app.views.NotificationDropdown = app.views.Base.extend({
|
|||
},
|
||||
|
||||
showDropdown: function(){
|
||||
this.resetParams();
|
||||
this.ajaxLoader.show();
|
||||
this.dropdown.addClass("dropdown-open");
|
||||
this.updateScrollbar();
|
||||
this.dropdownNotifications.addClass("loading");
|
||||
this.getNotifications();
|
||||
this.collection.fetch();
|
||||
},
|
||||
|
||||
hideDropdown: function(evt){
|
||||
|
|
@ -50,40 +54,18 @@ app.views.NotificationDropdown = app.views.Base.extend({
|
|||
|
||||
dropdownScroll: function(){
|
||||
var isLoading = ($(".loading").length === 1);
|
||||
if (this.isBottom() && this.hasMoreNotifs && !isLoading){
|
||||
if (this.isBottom() && !isLoading) {
|
||||
this.dropdownNotifications.addClass("loading");
|
||||
this.getNotifications();
|
||||
this.collection.fetchMore();
|
||||
}
|
||||
},
|
||||
|
||||
getParams: function(){
|
||||
if(this.notifications.length === 0){ return{ per_page: 10, page: 1 }; }
|
||||
else{ return{ per_page: this.perPage, page: this.nextPage }; }
|
||||
},
|
||||
|
||||
resetParams: function(){
|
||||
this.notifications.length = 0;
|
||||
this.hasMoreNotifs = true;
|
||||
delete this.nextPage;
|
||||
},
|
||||
|
||||
isBottom: function(){
|
||||
var bottom = this.dropdownNotifications.prop("scrollHeight") - this.dropdownNotifications.height();
|
||||
var currentPosition = this.dropdownNotifications.scrollTop();
|
||||
return currentPosition + 50 >= bottom;
|
||||
},
|
||||
|
||||
getNotifications: function(){
|
||||
var self = this;
|
||||
$.getJSON(Routes.notifications(this.getParams()), function(notifications){
|
||||
$.each(notifications, function(){ self.notifications.push(this); });
|
||||
self.hasMoreNotifs = notifications.length >= self.perPage;
|
||||
if(self.nextPage){ self.nextPage++; }
|
||||
else { self.nextPage = 3; }
|
||||
self.renderNotifications();
|
||||
});
|
||||
},
|
||||
|
||||
hideAjaxLoader: function(){
|
||||
var self = this;
|
||||
this.ajaxLoader.find(".spinner").fadeTo(200, 0, function(){
|
||||
|
|
@ -93,28 +75,23 @@ app.views.NotificationDropdown = app.views.Base.extend({
|
|||
});
|
||||
},
|
||||
|
||||
renderNotifications: function(){
|
||||
var self = this;
|
||||
this.dropdownNotifications.find(".media.stream-element").remove();
|
||||
$.each(self.notifications, function(index, notifications){
|
||||
$.each(notifications, function(index, notification){
|
||||
if($.inArray(notification, notifications) === -1){
|
||||
var node = self.dropdownNotifications.append(notification.note_html);
|
||||
onPushBack: function(notification) {
|
||||
var node = this.dropdownNotifications.append(notification.get("note_html"));
|
||||
$(node).find(".unread-toggle .entypo-eye").tooltip("destroy").tooltip();
|
||||
$(node).find(self.avatars.selector).error(self.avatars.fallback);
|
||||
}
|
||||
});
|
||||
});
|
||||
$(node).find(this.avatars.selector).error(this.avatars.fallback);
|
||||
},
|
||||
|
||||
this.hideAjaxLoader();
|
||||
onPushFront: function(notification) {
|
||||
var node = this.dropdownNotifications.prepend(notification.get("note_html"));
|
||||
$(node).find(".unread-toggle .entypo-eye").tooltip("destroy").tooltip();
|
||||
$(node).find(this.avatars.selector).error(this.avatars.fallback);
|
||||
},
|
||||
|
||||
finishLoading: function() {
|
||||
app.helpers.timeago(this.dropdownNotifications);
|
||||
|
||||
this.updateScrollbar();
|
||||
this.hideAjaxLoader();
|
||||
this.dropdownNotifications.removeClass("loading");
|
||||
this.dropdownNotifications.scroll(function(){
|
||||
self.dropdownScroll();
|
||||
});
|
||||
},
|
||||
|
||||
updateScrollbar: function() {
|
||||
|
|
|
|||
|
|
@ -1,96 +1,85 @@
|
|||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||
|
||||
app.views.Notifications = Backbone.View.extend({
|
||||
|
||||
events: {
|
||||
"click .unread-toggle": "toggleUnread",
|
||||
"click #mark_all_read_link": "markAllRead"
|
||||
"click #mark-all-read-link": "markAllRead"
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
$(".unread-toggle .entypo-eye").tooltip();
|
||||
app.helpers.timeago($(document));
|
||||
this.bindCollectionEvents();
|
||||
},
|
||||
|
||||
bindCollectionEvents: function() {
|
||||
this.collection.on("change", this.onChangedUnreadStatus.bind(this));
|
||||
this.collection.on("update", this.updateView.bind(this));
|
||||
},
|
||||
|
||||
toggleUnread: function(evt) {
|
||||
var note = $(evt.target).closest(".stream-element");
|
||||
var unread = note.hasClass("unread");
|
||||
var guid = note.data("guid");
|
||||
if (unread){ this.setRead(guid); }
|
||||
else { this.setUnread(guid); }
|
||||
if (unread) {
|
||||
this.collection.setRead(guid);
|
||||
} else {
|
||||
this.collection.setUnread(guid);
|
||||
}
|
||||
},
|
||||
|
||||
getAllUnread: function() { return $(".media.stream-element.unread"); },
|
||||
|
||||
setRead: function(guid) { this.setUnreadStatus(guid, false); },
|
||||
|
||||
setUnread: function(guid){ this.setUnreadStatus(guid, true); },
|
||||
|
||||
setUnreadStatus: function(guid, state){
|
||||
$.ajax({
|
||||
url: "/notifications/" + guid,
|
||||
data: { set_unread: state },
|
||||
type: "PUT",
|
||||
context: this,
|
||||
success: this.clickSuccess
|
||||
});
|
||||
markAllRead: function() {
|
||||
this.collection.setAllRead();
|
||||
},
|
||||
|
||||
clickSuccess: function(data) {
|
||||
var guid = data.guid;
|
||||
var type = $(".stream-element[data-guid=" + guid + "]").data("type");
|
||||
this.updateView(guid, type, data.unread);
|
||||
},
|
||||
onChangedUnreadStatus: function(model) {
|
||||
var unread = model.get("unread");
|
||||
var translationKey = unread ? "notifications.mark_read" : "notifications.mark_unread";
|
||||
var note = $(".stream-element[data-guid=" + model.guid + "]");
|
||||
|
||||
markAllRead: function(evt){
|
||||
if(evt) { evt.preventDefault(); }
|
||||
var self = this;
|
||||
this.getAllUnread().each(function(i, el){
|
||||
self.setRead($(el).data("guid"));
|
||||
});
|
||||
},
|
||||
|
||||
updateView: function(guid, type, unread) {
|
||||
var change = unread ? 1 : -1,
|
||||
allNotes = $("#notifications_container .list-group > a:eq(0) .badge"),
|
||||
typeNotes = $("#notifications_container .list-group > a[data-type=" + type + "] .badge"),
|
||||
headerBadge = $(".notifications-link .badge"),
|
||||
note = $(".notifications .stream-element[data-guid=" + guid + "]"),
|
||||
markAllReadLink = $("a#mark_all_read_link"),
|
||||
translationKey = unread ? "notifications.mark_read" : "notifications.mark_unread";
|
||||
|
||||
if(unread){ note.removeClass("read").addClass("unread"); }
|
||||
else { note.removeClass("unread").addClass("read"); }
|
||||
|
||||
$(".unread-toggle .entypo-eye", note)
|
||||
note.find(".entypo-eye")
|
||||
.tooltip("destroy")
|
||||
.removeAttr("data-original-title")
|
||||
.attr("title", Diaspora.I18n.t(translationKey))
|
||||
.tooltip();
|
||||
|
||||
[allNotes, typeNotes, headerBadge].forEach(function(element){
|
||||
element.text(function(i, text){
|
||||
return parseInt(text) + change;
|
||||
});
|
||||
});
|
||||
|
||||
[allNotes, typeNotes].forEach(function(badge) {
|
||||
if(badge.text() > 0) {
|
||||
badge.removeClass("hidden");
|
||||
if (unread) {
|
||||
note.removeClass("read").addClass("unread");
|
||||
} else {
|
||||
note.removeClass("unread").addClass("read");
|
||||
}
|
||||
else {
|
||||
badge.addClass("hidden");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
if(headerBadge.text() > 0){
|
||||
headerBadge.removeClass("hidden");
|
||||
updateView: function() {
|
||||
var notificationsContainer = $("#notifications_container");
|
||||
|
||||
// update notification counts in the sidebar
|
||||
Object.keys(this.collection.unreadCountByType).forEach(function(notificationType) {
|
||||
var count = this.collection.unreadCountByType[notificationType];
|
||||
this.updateBadge(notificationsContainer.find("a[data-type=" + notificationType + "] .badge"), count);
|
||||
}.bind(this));
|
||||
|
||||
this.updateBadge(notificationsContainer.find("a[data-type=all] .badge"), this.collection.unreadCount);
|
||||
|
||||
// update notification count in the header
|
||||
this.updateBadge($(".notifications-link .badge"), this.collection.unreadCount);
|
||||
|
||||
var markAllReadLink = $("a#mark-all-read-link");
|
||||
|
||||
if (this.collection.unreadCount > 0) {
|
||||
markAllReadLink.removeClass("disabled");
|
||||
}
|
||||
else{
|
||||
headerBadge.addClass("hidden");
|
||||
} else {
|
||||
markAllReadLink.addClass("disabled");
|
||||
}
|
||||
},
|
||||
|
||||
updateBadge: function(badge, count) {
|
||||
badge.text(count);
|
||||
if (count > 0) {
|
||||
badge.removeClass("hidden");
|
||||
} else {
|
||||
badge.addClass("hidden");
|
||||
}
|
||||
}
|
||||
});
|
||||
// @license-end
|
||||
|
|
|
|||
22
app/assets/javascripts/helpers/browser_notification.js
Normal file
22
app/assets/javascripts/helpers/browser_notification.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Diaspora.BrowserNotification = {
|
||||
requestPermission: function() {
|
||||
if ("Notification" in window && Notification.permission !== "granted" && Notification.permission !== "denied") {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
},
|
||||
|
||||
spawnNotification: function(title, summary) {
|
||||
if ("Notification" in window && Notification.permission === "granted") {
|
||||
if (!_.isString(title)) {
|
||||
throw new Error("No notification title given.");
|
||||
}
|
||||
|
||||
summary = summary || "";
|
||||
|
||||
new Notification(title, {
|
||||
body: summary,
|
||||
icon: ImagePaths.get("branding/logos/asterisk_white_mobile.png")
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
<ul class="dropdown-menu" role="menu">
|
||||
<div class="header">
|
||||
<div class="pull-right">
|
||||
<a href="#" id="mark_all_read_link" class="btn btn-default btn-sm {{#unless current_user.notifications_count}}disabled{{/unless}}">
|
||||
<a href="#" id="mark-all-read-link" class="btn btn-default btn-sm {{#unless current_user.notifications_count}}disabled{{/unless}}">
|
||||
{{t "header.mark_all_as_read"}}
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -61,11 +61,10 @@
|
|||
{{t "header.recent_notifications"}}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="notifications">
|
||||
<div class="ajax-loader">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notifications"></div>
|
||||
<div class="view_all">
|
||||
<a href="/notifications" id="view_all_notifications">
|
||||
{{t "header.view_all"}}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class NotificationsController < ApplicationController
|
|||
format.html
|
||||
format.xml { render :xml => @notifications.to_xml }
|
||||
format.json {
|
||||
render json: @notifications, each_serializer: NotificationSerializer
|
||||
render json: render_as_json(@unread_notification_count, @grouped_unread_notification_counts, @notifications)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -82,4 +82,15 @@ class NotificationsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_as_json(unread_count, unread_count_by_type, notification_list)
|
||||
{
|
||||
unread_count: unread_count,
|
||||
unread_count_by_type: unread_count_by_type,
|
||||
notification_list: notification_list.map {|note|
|
||||
NotificationSerializer.new(note, default_serializer_options).as_json
|
||||
}
|
||||
}.as_json
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
= t(".notifications")
|
||||
.list-group
|
||||
%a.list-group-item{href: "/notifications" + (params[:show] == "unread" ? "?show=unread" : ""),
|
||||
class: ("active" unless params[:type] && @grouped_unread_notification_counts.has_key?(params[:type]))}
|
||||
class: ("active" unless params[:type] && @grouped_unread_notification_counts.has_key?(params[:type])),
|
||||
data: {type: "all"}}
|
||||
%span.pull-right.badge{class: ("hidden" unless @unread_notification_count > 0)}
|
||||
= @unread_notification_count
|
||||
= t(".all_notifications")
|
||||
|
|
|
|||
|
|
@ -248,6 +248,9 @@ en:
|
|||
notifications:
|
||||
mark_read: "Mark read"
|
||||
mark_unread: "Mark unread"
|
||||
new_notifications:
|
||||
one: "You have <%= count %> unread notification"
|
||||
other: "You have <%= count %> unread notifications"
|
||||
|
||||
stream:
|
||||
hide: "Hide"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ describe NotificationsController, :type => :controller do
|
|||
it "generates a jasmine fixture", :fixture => true do
|
||||
get :index
|
||||
save_fixture(html_for("body"), "notifications")
|
||||
get :index, format: :json
|
||||
save_fixture(response.body, "notifications_collection")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -68,15 +68,24 @@ describe NotificationsController, :type => :controller do
|
|||
expect(assigns[:notifications].count).to eq(1)
|
||||
end
|
||||
|
||||
it 'succeeds for notification dropdown' do
|
||||
it "succeeds for notification dropdown" do
|
||||
Timecop.travel(6.seconds.ago) do
|
||||
@notification.touch
|
||||
end
|
||||
get :index, :format => :json
|
||||
get :index, format: :json
|
||||
expect(response).to be_success
|
||||
note_html = JSON.parse(response.body)[0]["also_commented"]["note_html"]
|
||||
note_html = Nokogiri::HTML(note_html)
|
||||
response_json = JSON.parse(response.body)
|
||||
note_html = Nokogiri::HTML(response_json["notification_list"][0]["also_commented"]["note_html"])
|
||||
timeago_content = note_html.css("time")[0]["data-time-ago"]
|
||||
expect(response_json["unread_count"]).to be(1)
|
||||
expect(response_json["unread_count_by_type"]).to eq(
|
||||
"also_commented" => 1,
|
||||
"comment_on_post" => 0,
|
||||
"liked" => 0,
|
||||
"mentioned" => 0,
|
||||
"reshared" => 0,
|
||||
"started_sharing" => 0
|
||||
)
|
||||
expect(timeago_content).to include(@notification.updated_at.iso8601)
|
||||
expect(response.body).to match(/note_html/)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,249 @@
|
|||
describe("app.collections.Notifications", function() {
|
||||
describe("initialize", function() {
|
||||
it("calls pollNotifications", function() {
|
||||
spyOn(app.collections.Notifications.prototype, "pollNotifications");
|
||||
new app.collections.Notifications();
|
||||
expect(app.collections.Notifications.prototype.pollNotifications).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls Diaspora.BrowserNotification.requestPermission", function() {
|
||||
spyOn(Diaspora.BrowserNotification, "requestPermission");
|
||||
new app.collections.Notifications();
|
||||
expect(Diaspora.BrowserNotification.requestPermission).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("initializes attributes", function() {
|
||||
var target = new app.collections.Notifications();
|
||||
expect(target.model).toBe(app.models.Notification);
|
||||
/* eslint-disable camelcase */
|
||||
expect(target.url).toBe(Routes.notifications({per_page: 10, page: 1}));
|
||||
/* eslint-enable camelcase */
|
||||
expect(target.page).toBe(2);
|
||||
expect(target.perPage).toBe(5);
|
||||
expect(target.unreadCount).toBe(0);
|
||||
expect(target.unreadCountByType).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("pollNotifications", function() {
|
||||
beforeEach(function() {
|
||||
this.target = new app.collections.Notifications();
|
||||
});
|
||||
|
||||
it("calls fetch", function() {
|
||||
spyOn(app.collections.Notifications.prototype, "fetch");
|
||||
this.target.pollNotifications();
|
||||
expect(app.collections.Notifications.prototype.fetch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("doesn't call Diaspora.BrowserNotification.spawnNotification when there are no new notifications", function() {
|
||||
spyOn(Diaspora.BrowserNotification, "spawnNotification");
|
||||
this.target.pollNotifications();
|
||||
this.target.trigger("finishedLoading");
|
||||
expect(Diaspora.BrowserNotification.spawnNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls Diaspora.BrowserNotification.spawnNotification when there are new notifications", function() {
|
||||
spyOn(Diaspora.BrowserNotification, "spawnNotification");
|
||||
spyOn(app.collections.Notifications.prototype, "fetch").and.callFake(function() {
|
||||
this.target.unreadCount++;
|
||||
}.bind(this));
|
||||
this.target.pollNotifications();
|
||||
this.target.trigger("finishedLoading");
|
||||
expect(Diaspora.BrowserNotification.spawnNotification).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("refreshes after timeout", function() {
|
||||
spyOn(app.collections.Notifications.prototype, "pollNotifications").and.callThrough();
|
||||
this.target.pollNotifications();
|
||||
expect(app.collections.Notifications.prototype.pollNotifications).toHaveBeenCalledTimes(1);
|
||||
jasmine.clock().tick(2 * this.target.timeout);
|
||||
expect(app.collections.Notifications.prototype.pollNotifications).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetch", function() {
|
||||
it("calls Backbone.Collection.prototype.fetch with correct parameters", function() {
|
||||
var target = new app.collections.Notifications();
|
||||
spyOn(Backbone.Collection.prototype, "fetch");
|
||||
target.fetch({foo: "bar", remove: "bar", merge: "bar", parse: "bar"});
|
||||
expect(Backbone.Collection.prototype.fetch.calls.mostRecent().args).toEqual([{
|
||||
foo: "bar",
|
||||
remove: false,
|
||||
merge: true,
|
||||
parse: true
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetchMore", function() {
|
||||
beforeEach(function() {
|
||||
this.target = new app.collections.Notifications();
|
||||
spyOn(app.collections.Notifications.prototype, "fetch");
|
||||
});
|
||||
|
||||
it("fetches notifications when there are more notifications to be fetched", function() {
|
||||
this.target.length = 15;
|
||||
this.target.fetchMore();
|
||||
/* eslint-disable camelcase */
|
||||
var route = Routes.notifications({per_page: 5, page: 3});
|
||||
/* eslint-enable camelcase */
|
||||
expect(app.collections.Notifications.prototype.fetch).toHaveBeenCalledWith({url: route, pushBack: true});
|
||||
expect(this.target.page).toBe(3);
|
||||
});
|
||||
|
||||
it("doesn't fetch notifications when there are no more notifications to be fetched", function() {
|
||||
this.target.length = 0;
|
||||
this.target.fetchMore();
|
||||
expect(app.collections.Notifications.prototype.fetch).not.toHaveBeenCalled();
|
||||
expect(this.target.page).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("set", function() {
|
||||
beforeEach(function() {
|
||||
this.target = new app.collections.Notifications();
|
||||
});
|
||||
|
||||
context("calls to Backbone.Collection.prototype.set", function() {
|
||||
beforeEach(function() {
|
||||
spyOn(Backbone.Collection.prototype, "set");
|
||||
});
|
||||
|
||||
it("calls app.collections.Notifications.prototype.set", function() {
|
||||
this.target.set([]);
|
||||
expect(Backbone.Collection.prototype.set).toHaveBeenCalledWith([], {at: 0});
|
||||
});
|
||||
|
||||
it("inserts the items at the beginning of the collection if option 'pushBack' is false", function() {
|
||||
this.target.length = 15;
|
||||
this.target.set([], {pushBack: false});
|
||||
expect(Backbone.Collection.prototype.set).toHaveBeenCalledWith([], {pushBack: false, at: 0});
|
||||
});
|
||||
|
||||
it("inserts the items at the end of the collection if option 'pushBack' is true", function() {
|
||||
this.target.length = 15;
|
||||
this.target.set([], {pushBack: true});
|
||||
expect(Backbone.Collection.prototype.set).toHaveBeenCalledWith([], {pushBack: true, at: 15});
|
||||
});
|
||||
});
|
||||
|
||||
context("events", function() {
|
||||
beforeEach(function() {
|
||||
spyOn(Backbone.Collection.prototype, "set").and.callThrough();
|
||||
spyOn(app.collections.Notifications.prototype, "trigger").and.callThrough();
|
||||
this.model1 = new app.models.Notification({"reshared": {id: 1}, "type": "reshared"});
|
||||
this.model2 = new app.models.Notification({"reshared": {id: 2}, "type": "reshared"});
|
||||
this.model3 = new app.models.Notification({"reshared": {id: 3}, "type": "reshared"});
|
||||
this.model4 = new app.models.Notification({"reshared": {id: 4}, "type": "reshared"});
|
||||
});
|
||||
|
||||
it("triggers a 'pushFront' event for each model in reverse order when option 'pushBack' is false", function() {
|
||||
this.target.set([this.model1, this.model2, this.model3, this.model4], {pushBack: false});
|
||||
|
||||
var calls = app.collections.Notifications.prototype.trigger.calls;
|
||||
|
||||
var index = calls.count() - 5;
|
||||
expect(calls.argsFor(index)).toEqual(["pushFront", this.model4]);
|
||||
expect(calls.argsFor(index + 1)).toEqual(["pushFront", this.model3]);
|
||||
expect(calls.argsFor(index + 2)).toEqual(["pushFront", this.model2]);
|
||||
expect(calls.argsFor(index + 3)).toEqual(["pushFront", this.model1]);
|
||||
});
|
||||
|
||||
it("triggers a 'pushBack' event for each model in normal order when option 'pushBack' is true", function() {
|
||||
this.target.set([this.model1, this.model2, this.model3, this.model4], {pushBack: true});
|
||||
|
||||
var calls = app.collections.Notifications.prototype.trigger.calls;
|
||||
|
||||
var index = calls.count() - 5;
|
||||
expect(calls.argsFor(index)).toEqual(["pushBack", this.model1]);
|
||||
expect(calls.argsFor(index + 1)).toEqual(["pushBack", this.model2]);
|
||||
expect(calls.argsFor(index + 2)).toEqual(["pushBack", this.model3]);
|
||||
expect(calls.argsFor(index + 3)).toEqual(["pushBack", this.model4]);
|
||||
});
|
||||
|
||||
it("triggers a 'finishedLoading' event at the end of the process", function() {
|
||||
this.target.set([]);
|
||||
expect(app.collections.Notifications.prototype.trigger).toHaveBeenCalledWith("finishedLoading");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parse", function() {
|
||||
beforeEach(function() {
|
||||
this.target = new app.collections.Notifications();
|
||||
});
|
||||
|
||||
it("sets the unreadCount and unreadCountByType attributes", function() {
|
||||
expect(this.target.unreadCount).toBe(0);
|
||||
expect(this.target.unreadCountByType).toEqual({});
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
this.target.parse({
|
||||
unread_count: 15,
|
||||
unread_count_by_type: {reshared: 6},
|
||||
notification_list: []
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
expect(this.target.unreadCount).toBe(15);
|
||||
expect(this.target.unreadCountByType).toEqual({reshared: 6});
|
||||
});
|
||||
|
||||
it("correctly parses the result", function() {
|
||||
/* eslint-disable camelcase */
|
||||
var parsed = this.target.parse({
|
||||
unread_count: 15,
|
||||
unread_count_by_type: {reshared: 6},
|
||||
notification_list: [{"reshared": {id: 1}, "type": "reshared"}]
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
expect(parsed.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("correctly binds the change:unread event", function() {
|
||||
spyOn(app.collections.Notifications.prototype, "onChangedUnreadStatus");
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
var parsed = this.target.parse({
|
||||
unread_count: 15,
|
||||
unread_count_by_type: {reshared: 6},
|
||||
notification_list: [{"reshared": {id: 1}, "type": "reshared"}]
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
parsed[0].set("unread", true);
|
||||
|
||||
expect(app.collections.Notifications.prototype.onChangedUnreadStatus).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("onChangedUnreadStatus", function() {
|
||||
it("increases the unread counts when model's unread attribute is true", function() {
|
||||
var target = new app.collections.Notifications();
|
||||
var model = new app.models.Notification({"reshared": {id: 1, unread: true}, "type": "reshared"});
|
||||
|
||||
target.unreadCount = 15;
|
||||
target.unreadCountByType.reshared = 6;
|
||||
|
||||
target.onChangedUnreadStatus(model);
|
||||
|
||||
expect(target.unreadCount).toBe(16);
|
||||
expect(target.unreadCountByType.reshared).toBe(7);
|
||||
});
|
||||
|
||||
it("decreases the unread counts when model's unread attribute is false", function() {
|
||||
var target = new app.collections.Notifications();
|
||||
var model = new app.models.Notification({"reshared": {id: 1, unread: false}, "type": "reshared"});
|
||||
|
||||
target.unreadCount = 15;
|
||||
target.unreadCountByType.reshared = 6;
|
||||
|
||||
target.onChangedUnreadStatus(model);
|
||||
|
||||
expect(target.unreadCount).toBe(14);
|
||||
expect(target.unreadCountByType.reshared).toBe(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
85
spec/javascripts/app/models/notification_spec.js
Normal file
85
spec/javascripts/app/models/notification_spec.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
describe("app.models.Notification", function() {
|
||||
beforeEach(function() {
|
||||
this.model = new app.models.Notification({
|
||||
"reshared": {},
|
||||
"type": "reshared"
|
||||
});
|
||||
});
|
||||
|
||||
describe("constructor", function() {
|
||||
it("calls parent constructor with the correct parameters", function() {
|
||||
spyOn(Backbone, "Model").and.callThrough();
|
||||
new app.models.Notification({attribute: "attribute"}, {option: "option"});
|
||||
expect(Backbone.Model).toHaveBeenCalledWith(
|
||||
{attribute: "attribute"},
|
||||
{option: "option", parse: true}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parse", function() {
|
||||
it("correctly parses the object", function() {
|
||||
var parsed = this.model.parse({
|
||||
"reshared": {
|
||||
"id": 45,
|
||||
"target_type": "Post",
|
||||
"target_id": 11,
|
||||
"recipient_id": 1,
|
||||
"unread": true,
|
||||
"created_at": "2015-10-27T19:56:30.000Z",
|
||||
"updated_at": "2015-10-27T19:56:30.000Z",
|
||||
"note_html": "<html/>"
|
||||
},
|
||||
"type": "reshared"
|
||||
});
|
||||
|
||||
expect(parsed).toEqual({
|
||||
"type": "reshared",
|
||||
"id": 45,
|
||||
"target_type": "Post",
|
||||
"target_id": 11,
|
||||
"recipient_id": 1,
|
||||
"unread": true,
|
||||
"created_at": "2015-10-27T19:56:30.000Z",
|
||||
"updated_at": "2015-10-27T19:56:30.000Z",
|
||||
"note_html": "<html/>"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("setRead", function() {
|
||||
it("calls setUnreadStatus with 'false'", function() {
|
||||
spyOn(app.models.Notification.prototype, "setUnreadStatus");
|
||||
new app.models.Notification({"reshared": {}, "type": "reshared"}).setRead();
|
||||
expect(app.models.Notification.prototype.setUnreadStatus).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setUnread", function() {
|
||||
it("calls setUnreadStatus with 'true'", function() {
|
||||
spyOn(app.models.Notification.prototype, "setUnreadStatus");
|
||||
new app.models.Notification({"reshared": {}, "type": "reshared"}).setUnread();
|
||||
expect(app.models.Notification.prototype.setUnreadStatus).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setUnreadStatus", function() {
|
||||
beforeEach(function() {
|
||||
this.target = new app.models.Notification({"reshared": {id: 16}, "type": "reshared"});
|
||||
spyOn(app.models.Notification.prototype, "set").and.callThrough();
|
||||
});
|
||||
|
||||
it("calls calls ajax with correct parameters and sets 'unread' attribute", function() {
|
||||
this.target.setUnreadStatus(true);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({status: 200, responseText: '{"guid": 16, "unread": true}'});
|
||||
var call = jasmine.Ajax.requests.mostRecent();
|
||||
|
||||
expect(call.url).toBe("/notifications/16");
|
||||
/* eslint-disable camelcase */
|
||||
expect(call.params).toEqual("set_unread=true");
|
||||
/* eslint-enable camelcase */
|
||||
expect(call.method).toEqual("PUT");
|
||||
expect(app.models.Notification.prototype.set).toHaveBeenCalledWith("unread", true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -6,6 +6,7 @@ describe("app.views.Header", function() {
|
|||
|
||||
spec.loadFixture("aspects_index");
|
||||
gon.appConfig = {settings: {podname: "MyPod"}};
|
||||
app.notificationsCollection = new app.collections.Notifications();
|
||||
this.view = new app.views.Header().render();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -6,16 +6,35 @@ describe("app.views.NotificationDropdown", function() {
|
|||
$("header").prepend(this.header.el);
|
||||
loginAs({guid: "foo"});
|
||||
this.header.render();
|
||||
this.view = new app.views.NotificationDropdown({el: "#notification-dropdown"});
|
||||
this.collection = new app.collections.Notifications();
|
||||
this.view = new app.views.NotificationDropdown({el: "#notification-dropdown", collection: this.collection});
|
||||
});
|
||||
|
||||
context("showDropdown", function(){
|
||||
it("Calls resetParam()", function(){
|
||||
spyOn(this.view, "resetParams");
|
||||
this.view.showDropdown();
|
||||
expect(this.view.resetParams).toHaveBeenCalled();
|
||||
describe("bindCollectionEvents", function() {
|
||||
beforeEach(function() {
|
||||
this.view.collection.off("pushFront");
|
||||
this.view.collection.off("pushBack");
|
||||
this.view.collection.off("finishedLoading");
|
||||
spyOn(this.view, "onPushFront");
|
||||
spyOn(this.view, "onPushBack");
|
||||
spyOn(this.view, "finishLoading");
|
||||
});
|
||||
it("Calls updateScrollbar()", function(){
|
||||
|
||||
it("binds collection events", function() {
|
||||
this.view.bindCollectionEvents();
|
||||
|
||||
this.collection.trigger("pushFront");
|
||||
this.collection.trigger("pushBack");
|
||||
this.collection.trigger("finishedLoading");
|
||||
|
||||
expect(this.view.onPushFront).toHaveBeenCalled();
|
||||
expect(this.view.onPushBack).toHaveBeenCalled();
|
||||
expect(this.view.finishLoading).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("showDropdown", function() {
|
||||
it("Calls updateScrollbar", function() {
|
||||
spyOn(this.view, "updateScrollbar");
|
||||
this.view.showDropdown();
|
||||
expect(this.view.updateScrollbar).toHaveBeenCalled();
|
||||
|
|
@ -25,100 +44,30 @@ describe("app.views.NotificationDropdown", function() {
|
|||
this.view.showDropdown();
|
||||
expect($("#notification-dropdown")).toHaveClass("dropdown-open");
|
||||
});
|
||||
it("Calls getNotifications()", function(){
|
||||
spyOn(this.view, "getNotifications");
|
||||
it("Calls collection#fetch", function() {
|
||||
spyOn(this.collection, "fetch");
|
||||
this.view.showDropdown();
|
||||
expect(this.view.getNotifications).toHaveBeenCalled();
|
||||
expect(this.collection.fetch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
context("dropdownScroll", function(){
|
||||
it("Calls getNotifications if is at the bottom and has more notifications to load", function(){
|
||||
describe("dropdownScroll", function() {
|
||||
it("Calls collection#fetchMore if it is at the bottom", function() {
|
||||
this.view.isBottom = function() { return true; };
|
||||
this.view.hasMoreNotifs = true;
|
||||
spyOn(this.view, "getNotifications");
|
||||
spyOn(this.collection, "fetchMore");
|
||||
this.view.dropdownScroll();
|
||||
expect(this.view.getNotifications).toHaveBeenCalled();
|
||||
expect(this.collection.fetchMore).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Doesn't call getNotifications if is not at the bottom", function(){
|
||||
it("Doesn't call collection#fetchMore if it is not at the bottom", function() {
|
||||
this.view.isBottom = function() { return false; };
|
||||
this.view.hasMoreNotifs = true;
|
||||
spyOn(this.view, "getNotifications");
|
||||
spyOn(this.collection, "fetchMore");
|
||||
this.view.dropdownScroll();
|
||||
expect(this.view.getNotifications).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Doesn't call getNotifications if is not at the bottom", function(){
|
||||
this.view.isBottom = function(){ return true; };
|
||||
this.view.hasMoreNotifs = false;
|
||||
spyOn(this.view, "getNotifications");
|
||||
this.view.dropdownScroll();
|
||||
expect(this.view.getNotifications).not.toHaveBeenCalled();
|
||||
expect(this.collection.fetchMore).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
context("getNotifications", function(){
|
||||
it("Has more notifications", function(){
|
||||
var response = ["", "", "", "", ""];
|
||||
spyOn($, "getJSON").and.callFake(function(url, callback){ callback(response); });
|
||||
this.view.getNotifications();
|
||||
expect(this.view.hasMoreNotifs).toBe(true);
|
||||
});
|
||||
it("Has no more notifications", function(){
|
||||
spyOn($, "getJSON").and.callFake(function(url, callback){ callback([]); });
|
||||
this.view.getNotifications();
|
||||
expect(this.view.hasMoreNotifs).toBe(false);
|
||||
});
|
||||
it("Correctly sets the next page", function(){
|
||||
spyOn($, "getJSON").and.callFake(function(url, callback){ callback([]); });
|
||||
expect(typeof this.view.nextPage).toBe("undefined");
|
||||
this.view.getNotifications();
|
||||
expect(this.view.nextPage).toBe(3);
|
||||
});
|
||||
it("Increase the page count", function(){
|
||||
var response = ["", "", "", "", ""];
|
||||
spyOn($, "getJSON").and.callFake(function(url, callback){ callback(response); });
|
||||
this.view.getNotifications();
|
||||
expect(this.view.nextPage).toBe(3);
|
||||
this.view.getNotifications();
|
||||
expect(this.view.nextPage).toBe(4);
|
||||
});
|
||||
it("Calls renderNotifications()", function(){
|
||||
spyOn($, "getJSON").and.callFake(function(url, callback){ callback([]); });
|
||||
spyOn(this.view, "renderNotifications");
|
||||
this.view.getNotifications();
|
||||
expect(this.view.renderNotifications).toHaveBeenCalled();
|
||||
});
|
||||
it("Adds the notifications to this.notifications", function(){
|
||||
var response = ["", "", "", "", ""];
|
||||
this.view.notifications.length = 0;
|
||||
spyOn($, "getJSON").and.callFake(function(url, callback){ callback(response); });
|
||||
this.view.getNotifications();
|
||||
expect(this.view.notifications).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
context("renderNotifications", function(){
|
||||
it("Removes the previous notifications", function(){
|
||||
this.view.dropdownNotifications.append("<div class=\"media stream-element\">Notification</div>");
|
||||
expect(this.view.dropdownNotifications.find(".media.stream-element").length).toBe(1);
|
||||
this.view.renderNotifications();
|
||||
expect(this.view.dropdownNotifications.find(".media.stream-element").length).toBe(0);
|
||||
});
|
||||
it("Calls hideAjaxLoader()", function(){
|
||||
spyOn(this.view, "hideAjaxLoader");
|
||||
this.view.renderNotifications();
|
||||
expect(this.view.hideAjaxLoader).toHaveBeenCalled();
|
||||
});
|
||||
it("Calls updateScrollbar()", function(){
|
||||
spyOn(this.view, "updateScrollbar");
|
||||
this.view.renderNotifications();
|
||||
expect(this.view.updateScrollbar).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
context("updateScrollbar", function() {
|
||||
describe("updateScrollbar", function() {
|
||||
it("Initializes perfectScrollbar", function() {
|
||||
this.view.perfectScrollbarInitialized = false;
|
||||
spyOn($.fn, "perfectScrollbar");
|
||||
|
|
@ -139,7 +88,7 @@ describe("app.views.NotificationDropdown", function() {
|
|||
});
|
||||
});
|
||||
|
||||
context("destroyScrollbar", function() {
|
||||
describe("destroyScrollbar", function() {
|
||||
it("destroys perfectScrollbar", function() {
|
||||
this.view.perfectScrollbarInitialized = true;
|
||||
this.view.dropdownNotifications.perfectScrollbar();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,36 @@
|
|||
describe("app.views.Notifications", function() {
|
||||
beforeEach(function() {
|
||||
this.collection = new app.collections.Notifications();
|
||||
this.collection.fetch();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
responseText: spec.readFixture("notifications_collection")
|
||||
});
|
||||
});
|
||||
|
||||
context("on the notifications page", function() {
|
||||
beforeEach(function() {
|
||||
spec.loadFixture("notifications");
|
||||
this.view = new app.views.Notifications({el: "#notifications_container"});
|
||||
this.view = new app.views.Notifications({el: "#notifications_container", collection: this.collection});
|
||||
});
|
||||
|
||||
describe("bindCollectionEvents", function() {
|
||||
beforeEach(function() {
|
||||
this.view.collection.off("change");
|
||||
this.view.collection.off("update");
|
||||
spyOn(this.view, "onChangedUnreadStatus");
|
||||
spyOn(this.view, "updateView");
|
||||
});
|
||||
|
||||
it("binds collection events", function() {
|
||||
this.view.bindCollectionEvents();
|
||||
|
||||
this.collection.trigger("change");
|
||||
this.collection.trigger("update");
|
||||
|
||||
expect(this.view.onChangedUnreadStatus).toHaveBeenCalled();
|
||||
expect(this.view.updateView).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("mark read", function() {
|
||||
|
|
@ -11,11 +39,11 @@ describe("app.views.Notifications", function(){
|
|||
this.guid = this.unreadN.data("guid");
|
||||
});
|
||||
|
||||
it("calls 'setRead'", function() {
|
||||
spyOn(this.view, "setRead");
|
||||
it("calls collection's 'setRead'", function() {
|
||||
spyOn(this.collection, "setRead");
|
||||
this.unreadN.find(".unread-toggle").trigger("click");
|
||||
|
||||
expect(this.view.setRead).toHaveBeenCalledWith(this.guid);
|
||||
expect(this.collection.setRead).toHaveBeenCalledWith(this.guid);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -25,11 +53,11 @@ describe("app.views.Notifications", function(){
|
|||
this.guid = this.readN.data("guid");
|
||||
});
|
||||
|
||||
it("calls 'setUnread'", function() {
|
||||
spyOn(this.view, "setUnread");
|
||||
it("calls collection's 'setUnread'", function() {
|
||||
spyOn(this.collection, "setUnread");
|
||||
this.readN.find(".unread-toggle").trigger("click");
|
||||
|
||||
expect(this.view.setUnread).toHaveBeenCalledWith(this.guid);
|
||||
expect(this.collection.setUnread).toHaveBeenCalledWith(this.guid);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -40,42 +68,65 @@ describe("app.views.Notifications", function(){
|
|||
this.type = this.readN.data("type");
|
||||
});
|
||||
|
||||
it("changes the 'all notifications' count", function() {
|
||||
it("increases the 'all notifications' count", function() {
|
||||
var badge = $(".list-group > a:eq(0) .badge");
|
||||
var count = parseInt(badge.text());
|
||||
expect(parseInt(badge.text(), 10)).toBe(2);
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
expect(parseInt(badge.text())).toBe(count + 1);
|
||||
this.collection.unreadCount++;
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge.text(), 10)).toBe(3);
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
expect(parseInt(badge.text())).toBe(count);
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge.text(), 10)).toBe(3);
|
||||
});
|
||||
|
||||
it("changes the notification type count", function() {
|
||||
it("decreases the 'all notifications' count", function() {
|
||||
var badge = $(".list-group > a:eq(0) .badge");
|
||||
expect(parseInt(badge.text(), 10)).toBe(2);
|
||||
|
||||
this.collection.unreadCount--;
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge.text(), 10)).toBe(1);
|
||||
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge.text(), 10)).toBe(1);
|
||||
});
|
||||
|
||||
it("increases the notification type count", function() {
|
||||
var badge = $(".list-group > a[data-type=" + this.type + "] .badge");
|
||||
var count = parseInt(badge.text());
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
expect(parseInt(badge.text())).toBe(count + 1);
|
||||
expect(parseInt(badge.text(), 10)).toBe(1);
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
expect(parseInt(badge.text())).toBe(count);
|
||||
this.collection.unreadCountByType[this.type]++;
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge.text(), 10)).toBe(2);
|
||||
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge.text(), 10)).toBe(2);
|
||||
});
|
||||
|
||||
it("toggles the unread class and changes the title", function() {
|
||||
this.view.updateView(this.readN.data("guid"), this.readN.data("type"), true);
|
||||
expect(this.readN.hasClass("unread")).toBeTruthy();
|
||||
expect(this.readN.hasClass("read")).toBeFalsy();
|
||||
expect(this.readN.find(".unread-toggle .entypo-eye").attr("data-original-title")).toBe(
|
||||
Diaspora.I18n.t("notifications.mark_read")
|
||||
);
|
||||
it("decreases the notification type count", function() {
|
||||
var badge = $(".list-group > a[data-type=" + this.type + "] .badge");
|
||||
|
||||
this.view.updateView(this.readN.data("guid"), this.readN.data("type"), false);
|
||||
expect(this.readN.hasClass("read")).toBeTruthy();
|
||||
expect(this.readN.hasClass("unread")).toBeFalsy();
|
||||
expect(this.readN.find(".unread-toggle .entypo-eye").attr("data-original-title")).toBe(
|
||||
Diaspora.I18n.t("notifications.mark_unread")
|
||||
);
|
||||
expect(parseInt(badge.text(), 10)).toBe(1);
|
||||
|
||||
this.collection.unreadCountByType[this.type]--;
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge.text(), 10)).toBe(0);
|
||||
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge.text(), 10)).toBe(0);
|
||||
});
|
||||
|
||||
it("hides badge count when notification count is zero", function() {
|
||||
Object.keys(this.collection.unreadCountByType).forEach(function(notificationType) {
|
||||
this.collection.unreadCountByType[notificationType] = 0;
|
||||
}.bind(this));
|
||||
this.collection.unreadCount = 0;
|
||||
|
||||
this.view.updateView();
|
||||
|
||||
expect($("a .badge")).toHaveClass("hidden");
|
||||
});
|
||||
|
||||
context("with a header", function() {
|
||||
|
|
@ -84,6 +135,7 @@ describe("app.views.Notifications", function(){
|
|||
loginAs({name: "alice", avatar: {small: "http://avatar.com/photo.jpg"}, notifications_count: 2, guid: "foo"});
|
||||
/* jshint camelcase: true */
|
||||
gon.appConfig = {settings: {podname: "MyPod"}};
|
||||
app.notificationsCollection = this.collection;
|
||||
this.header = new app.views.Header();
|
||||
$("header").prepend(this.header.el);
|
||||
this.header.render();
|
||||
|
|
@ -92,30 +144,77 @@ describe("app.views.Notifications", function(){
|
|||
it("changes the header notifications count", function() {
|
||||
var badge1 = $(".notifications-link:eq(0) .badge");
|
||||
var badge2 = $(".notifications-link:eq(1) .badge");
|
||||
var count = parseInt(badge1.text(), 10);
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
expect(parseInt(badge1.text(), 10)).toBe(count + 1);
|
||||
expect(parseInt(badge1.text(), 10)).toBe(this.collection.unreadCount);
|
||||
expect(parseInt(badge2.text(), 10)).toBe(this.collection.unreadCount);
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
expect(parseInt(badge1.text(), 10)).toBe(count);
|
||||
this.collection.unreadCount++;
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge1.text(), 10)).toBe(this.collection.unreadCount);
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
expect(parseInt(badge2.text(), 10)).toBe(count + 1);
|
||||
this.view.updateView();
|
||||
expect(parseInt(badge2.text(), 10)).toBe(this.collection.unreadCount);
|
||||
});
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
expect(parseInt(badge2.text(), 10)).toBe(count);
|
||||
it("disables the mark-all-read-link button", function() {
|
||||
expect($("a#mark-all-read-link")).not.toHaveClass("disabled");
|
||||
this.collection.unreadCount = 0;
|
||||
this.view.updateView();
|
||||
expect($("a#mark-all-read-link")).toHaveClass("disabled");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("markAllRead", function() {
|
||||
it("calls setRead for each unread notification", function(){
|
||||
spyOn(this.view, "setRead");
|
||||
it("calls collection#setAllRead", function() {
|
||||
spyOn(this.collection, "setAllRead");
|
||||
this.view.markAllRead();
|
||||
expect(this.view.setRead).toHaveBeenCalledWith(this.view.$(".stream-element.unread").eq(0).data("guid"));
|
||||
this.view.markAllRead();
|
||||
expect(this.view.setRead).toHaveBeenCalledWith(this.view.$(".stream-element.unread").eq(1).data("guid"));
|
||||
expect(this.collection.setAllRead).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("onChangedUnreadStatus", function() {
|
||||
beforeEach(function() {
|
||||
this.modelRead = new app.models.Notification({});
|
||||
this.modelRead.set("unread", false);
|
||||
this.modelRead.guid = $(".stream-element.unread").first().data("guid");
|
||||
this.modelUnread = new app.models.Notification({});
|
||||
this.modelUnread.set("unread", true);
|
||||
this.modelUnread.guid = $(".stream-element.read").first().data("guid");
|
||||
});
|
||||
|
||||
it("Adds the unread class and changes the title", function() {
|
||||
var unreadEl = $(".stream-element[data-guid=" + this.modelUnread.guid + "]");
|
||||
|
||||
expect(unreadEl.hasClass("read")).toBeTruthy();
|
||||
expect(unreadEl.hasClass("unread")).toBeFalsy();
|
||||
expect(unreadEl.find(".unread-toggle .entypo-eye").attr("data-original-title")).toBe(
|
||||
Diaspora.I18n.t("notifications.mark_unread")
|
||||
);
|
||||
|
||||
this.view.onChangedUnreadStatus(this.modelUnread);
|
||||
expect(unreadEl.hasClass("unread")).toBeTruthy();
|
||||
expect(unreadEl.hasClass("read")).toBeFalsy();
|
||||
expect(unreadEl.find(".unread-toggle .entypo-eye").attr("data-original-title")).toBe(
|
||||
Diaspora.I18n.t("notifications.mark_read")
|
||||
);
|
||||
});
|
||||
|
||||
it("Removes the unread class and changes the title", function() {
|
||||
var readEl = $(".stream-element[data-guid=" + this.modelRead.guid + "]");
|
||||
|
||||
expect(readEl.hasClass("unread")).toBeTruthy();
|
||||
expect(readEl.hasClass("read")).toBeFalsy();
|
||||
expect(readEl.find(".unread-toggle .entypo-eye").attr("data-original-title")).toBe(
|
||||
Diaspora.I18n.t("notifications.mark_read")
|
||||
);
|
||||
|
||||
this.view.onChangedUnreadStatus(this.modelRead);
|
||||
expect(readEl.hasClass("read")).toBeTruthy();
|
||||
expect(readEl.hasClass("unread")).toBeFalsy();
|
||||
expect(readEl.find(".unread-toggle .entypo-eye").attr("data-original-title")).toBe(
|
||||
Diaspora.I18n.t("notifications.mark_unread")
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -123,47 +222,32 @@ describe("app.views.Notifications", function(){
|
|||
context("on the contacts page", function() {
|
||||
beforeEach(function() {
|
||||
spec.loadFixture("aspects_manage");
|
||||
this.view = new app.views.Notifications({el: "#notifications_container"});
|
||||
this.view = new app.views.Notifications({el: "#notifications_container", collection: this.collection});
|
||||
/* jshint camelcase: false */
|
||||
loginAs({name: "alice", avatar: {small: "http://avatar.com/photo.jpg"}, notifications_count: 2, guid: "foo"});
|
||||
/* jshint camelcase: true */
|
||||
gon.appConfig = {settings: {podname: "MyPod"}};
|
||||
app.notificationsCollection = this.collection;
|
||||
this.header = new app.views.Header();
|
||||
$("header").prepend(this.header.el);
|
||||
this.header.render();
|
||||
});
|
||||
|
||||
describe("updateView", function() {
|
||||
it("changes the header notifications count", function() {
|
||||
var badge1 = $(".notifications-link:eq(0) .badge");
|
||||
var badge2 = $(".notifications-link:eq(1) .badge");
|
||||
var count = parseInt(badge1.text(), 10);
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
expect(parseInt(badge1.text(), 10)).toBe(count + 1);
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
expect(parseInt(badge1.text(), 10)).toBe(count);
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
expect(parseInt(badge2.text(), 10)).toBe(count + 1);
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
expect(parseInt(badge2.text(), 10)).toBe(count);
|
||||
});
|
||||
|
||||
it("doesn't change the contacts count", function() {
|
||||
expect($("#aspect_nav .badge").length).toBeGreaterThan(0);
|
||||
$("#aspect_nav .badge").each(function(index, el) {
|
||||
$(el).text(index + 1337);
|
||||
});
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
this.view.updateView();
|
||||
$("#aspect_nav .badge").each(function(index, el) {
|
||||
expect(parseInt($(el).text(), 10)).toBe(index + 1337);
|
||||
});
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
this.collection.unreadCount++;
|
||||
|
||||
this.view.updateView();
|
||||
$("#aspect_nav .badge").each(function(index, el) {
|
||||
expect(parseInt($(el).text(), 10)).toBe(index + 1337);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue