Bug 987252 - Using new shared notification system. r=Standard8

This commit is contained in:
Nicolas Perriault 2014-05-29 21:13:43 +01:00
parent 3569e14679
commit 1513560b26
7 changed files with 108 additions and 162 deletions

View File

@ -10,6 +10,9 @@
<link rel="stylesheet" type="text/css" href="shared/css/conversation.css">
</head>
<body onload="loop.conversation.init();">
<div id="messages"></div>
<div id="conversation" class="conversation">
<div class="media nested">
<div class="remote">
@ -20,6 +23,8 @@
</div>
</div>
</div>
<script type="text/javascript" src="libs/l10n.js"></script>
<script type="text/javascript" src="shared/libs/sdk.js"></script>
<script type="text/javascript" src="shared/libs/jquery-2.1.0.js"></script>
<script type="text/javascript" src="shared/libs/lodash-2.4.1.js"></script>

View File

@ -2,15 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/*global loop*/
/* global loop:true */
Components.utils.import("resource://gre/modules/Services.jsm");
var loop = loop || {};
loop.conversation = (function(TB) {
loop.conversation = (function(TB, mozl10n) {
"use strict";
var baseServerUrl = Services.prefs.getCharPref("loop.server");
var baseServerUrl = Services.prefs.getCharPref("loop.server"),
// aliasing translation function as __ for concision
__ = mozl10n.get;
/**
* App router.
@ -24,9 +26,17 @@ loop.conversation = (function(TB) {
*/
var conversation;
/**
* Conversation router.
*
* Required options:
* - {loop.shared.models.ConversationModel} conversation Conversation model.
* - {loop.shared.components.Notifier} notifier Notifier component.
*/
var ConversationRouter = loop.shared.router.BaseRouter.extend({
_conversation: undefined,
activeView: undefined,
_notifier: undefined,
activeView: undefined,
routes: {
"start/:version": "start",
@ -53,6 +63,11 @@ loop.conversation = (function(TB) {
}
this._conversation = options.conversation;
if (!options.notifier) {
throw new Error("missing required notifier");
}
this._notifier = options.notifier;
this.listenTo(this._conversation, "session:ready", this._onSessionReady);
this.listenTo(this._conversation, "session:ended", this._onSessionEnded);
},
@ -97,6 +112,7 @@ loop.conversation = (function(TB) {
// XXX: notify user that something has gone wrong.
console.error("Error: navigated to conversation route without " +
"the start route to initialise the call first");
this._notifier.notify(__("cannot_start_call_session_not_ready"));
return;
}
@ -121,7 +137,12 @@ loop.conversation = (function(TB) {
*/
function init() {
conversation = new loop.shared.models.ConversationModel();
router = new ConversationRouter({conversation: conversation});
router = new ConversationRouter({
conversation: conversation,
notifier: new loop.shared.views.NotificationListView({
el: "#messages"
})
});
Backbone.history.start();
}
@ -129,4 +150,4 @@ loop.conversation = (function(TB) {
ConversationRouter: ConversationRouter,
init: init
};
})(window.TB);
})(window.TB, document.mozL10n);

View File

@ -2,91 +2,23 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/*global loop*/
/* global loop:true */
Components.utils.import("resource://gre/modules/Services.jsm");
var loop = loop || {};
loop.panel = (function(_, __) {
loop.panel = (function(TB, mozl10n) {
"use strict";
var baseServerUrl = Services.prefs.getCharPref("loop.server"),
panelView;
/**
* Panel initialisation.
*/
function init() {
panelView = new PanelView();
panelView.render();
}
/**
* Notification model.
*/
var NotificationModel = Backbone.Model.extend({
defaults: {
level: "info",
message: ""
}
});
/**
* Notification collection
*/
var NotificationCollection = Backbone.Collection.extend({
model: NotificationModel
});
/**
* Notification view.
*/
var NotificationView = Backbone.View.extend({
template: _.template([
'<div class="alert alert-<%- level %>">',
' <button class="close"></button>',
' <p class="message"><%- message %></p>',
'</div>'
].join("")),
events: {
"click .close": "dismiss"
},
dismiss: function() {
this.$el.addClass("fade-out");
setTimeout(function() {
this.collection.remove(this.model);
this.remove();
}.bind(this), 500);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
/**
* Notification list view.
*/
var NotificationListView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.collection, "reset add remove", this.render);
},
render: function() {
this.$el.html(this.collection.map(function(notification) {
return new NotificationView({
model: notification,
collection: this.collection
}).render().$el;
}.bind(this)));
return this;
}
});
panelView,
// aliasing translation function as __ for concision
__ = mozl10n.get;
/**
* Panel view.
*
* XXX view layout changes should be handled by a router really.
*/
var PanelView = Backbone.View.extend({
el: "#default-view",
@ -101,19 +33,9 @@ loop.panel = (function(_, __) {
this.client = new loop.shared.Client({
baseServerUrl: baseServerUrl
});
this.notificationCollection = new NotificationCollection();
this.notificationListView = new NotificationListView({
el: this.$(".messages"),
collection: this.notificationCollection
});
this.notificationListView.render();
},
notify: function(message, level) {
this.notificationCollection.add({
level: level || "info",
message: message
});
this.notifier = new loop.shared.views.NotificationListView({
el: this.$(".messages")
}).render();
},
getCallUrl: function(event) {
@ -121,7 +43,10 @@ loop.panel = (function(_, __) {
var nickname = this.$("input[name=caller]").val();
var callback = function(err, callUrl) {
if (err) {
this.notify(__("unable_retrieve_url"), "error");
this.notifier.notify({
message: __("unable_retrieve_url"),
level: "error"
});
return;
}
this.onCallUrlReceived(callUrl);
@ -137,7 +62,7 @@ loop.panel = (function(_, __) {
},
onCallUrlReceived: function(callUrl) {
this.notificationCollection.reset();
this.notifier.clear();
this.$(".action .invite").hide();
this.$(".action .invite input").val("");
this.$(".action .result input").val(callUrl);
@ -154,12 +79,16 @@ loop.panel = (function(_, __) {
}
});
/**
* Panel initialisation.
*/
function init() {
panelView = new PanelView();
panelView.render();
}
return {
init: init,
NotificationModel: NotificationModel,
NotificationCollection: NotificationCollection,
NotificationView: NotificationView,
NotificationListView: NotificationListView,
PanelView: PanelView
};
})(_, document.mozL10n.get);
})(_, document.mozL10n);

View File

@ -45,6 +45,8 @@
<script type="text/javascript" src="shared/libs/lodash-2.4.1.js"></script>
<script type="text/javascript" src="shared/libs/backbone-1.1.2.js"></script>
<script type="text/javascript" src="shared/js/client.js"></script>
<script type="text/javascript" src="shared/js/models.js"></script>
<script type="text/javascript" src="shared/js/views.js"></script>
<script type="text/javascript" src="js/panel.js"></script>
</body>
</html>

View File

@ -10,10 +10,12 @@ describe("loop.conversation", function() {
"use strict";
var ConversationRouter = loop.conversation.ConversationRouter,
sandbox;
sandbox,
notifier;
beforeEach(function() {
sandbox = sinon.sandbox.create();
notifier = {notify: sandbox.spy()};
});
afterEach(function() {
@ -33,13 +35,22 @@ describe("loop.conversation", function() {
new ConversationRouter();
}).to.Throw(Error, /missing required conversation/);
});
it("should require a notifier", function() {
expect(function() {
new ConversationRouter({conversation: conversation});
}).to.Throw(Error, /missing required notifier/);
});
});
describe("Routes", function() {
var router;
beforeEach(function() {
router = new ConversationRouter({conversation: conversation});
router = new ConversationRouter({
conversation: conversation,
notifier: notifier
});
sandbox.stub(router, "loadView");
});
@ -83,6 +94,13 @@ describe("loop.conversation", function() {
sinon.assert.notCalled(router.loadView);
});
it("should notify the user when session is not set",
function() {
router.conversation();
sinon.assert.calledOnce(router._notifier.notify);
});
});
describe("#ended", function() {
@ -113,7 +131,8 @@ describe("loop.conversation", function() {
function() {
sandbox.stub(ConversationRouter.prototype, "navigate");
var router = new ConversationRouter({
conversation: conversation
conversation: conversation,
notifier: notifier
});
conversation.setReady(fakeSessionData);
@ -126,7 +145,8 @@ describe("loop.conversation", function() {
function() {
sandbox.stub(ConversationRouter.prototype, "navigate");
var router = new ConversationRouter({
conversation: conversation
conversation: conversation,
notifier: notifier
});
conversation.trigger("session:ended");

View File

@ -26,61 +26,6 @@ describe("loop.panel", function() {
sandbox.restore();
});
describe("loop.panel.NotificationView", function() {
describe("#render", function() {
it("should render template with model attribute values", function() {
var view = new loop.panel.NotificationView({
el: $("#fixtures"),
model: new loop.panel.NotificationModel({
level: "error",
message: "plop"
})
});
view.render();
expect(view.$(".message").text()).eql("plop");
});
});
});
describe("loop.panel.NotificationListView", function() {
describe("Collection events", function() {
var coll, testNotif, view;
beforeEach(function() {
sandbox.stub(loop.panel.NotificationListView.prototype, "render");
testNotif = new loop.panel.NotificationModel({
level: "error",
message: "plop"
});
coll = new loop.panel.NotificationCollection();
view = new loop.panel.NotificationListView({collection: coll});
});
it("should render on notification added to the collection", function() {
coll.add(testNotif);
sinon.assert.calledOnce(view.render);
});
it("should render on notification removed from the collection",
function() {
coll.add(testNotif);
coll.remove(testNotif);
sinon.assert.calledTwice(view.render);
});
it("should render on collection reset",
function() {
coll.reset();
sinon.assert.calledOnce(view.render);
});
});
});
describe("loop.panel.PanelView", function() {
beforeEach(function() {
$("#fixtures").append([
@ -104,7 +49,7 @@ describe("loop.panel", function() {
].join(""));
});
describe("#getCallurl", function() {
describe("#getCallUrl", function() {
it("should request a call url to the server", function() {
var requestCallUrl = sandbox.stub(loop.shared.Client.prototype,
"requestCallUrl");
@ -115,6 +60,20 @@ describe("loop.panel", function() {
sinon.assert.calledOnce(requestCallUrl);
sinon.assert.calledWith(requestCallUrl, "foo");
});
it("should notify the user when the operation failed", function() {
var requestCallUrl = sandbox.stub(
loop.shared.Client.prototype, "requestCallUrl", function(_, cb) {
cb("fake error");
});
var view = new loop.panel.PanelView();
sandbox.stub(view.notifier, "notify");
view.getCallUrl({preventDefault: sandbox.spy()});
sinon.assert.calledOnce(view.notifier.notify);
sinon.assert.calledWithMatch(view.notifier.notify, {level: "error"});
});
});
describe("#onCallUrlReceived", function() {
@ -126,6 +85,15 @@ describe("loop.panel", function() {
expect(view.$("#call-url").val()).eql("http://call.me/");
});
it("should reset all pending notifications", function() {
var view = new loop.panel.PanelView().render();
sandbox.stub(view.notifier, "clear");
view.onCallUrlReceived("http://call.me/");
sinon.assert.calledOnce(view.notifier.clear, "clear");
});
});
});
});

View File

@ -4,3 +4,4 @@ new_url=New url
unable_retrieve_url=Sorry, we were unable to retrieve a call url.
share_link_url=Share the link below with your friend to start your call!
caller.placeholder=Identify this call
cannot_start_call_session_not_ready=Can't start call, session is not ready.