mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1000131 - Expired Loop call url notification, r=dmose
--HG-- rename : browser/components/loop/standalone/content/js/webapp.js => browser/components/loop/standalone/content/js/webapp.jsx
This commit is contained in:
parent
79f86ed8e2
commit
3e357e0dc1
@ -117,8 +117,7 @@ loop.shared.models = (function() {
|
||||
this._clearPendingCallTimer();
|
||||
|
||||
if (err) {
|
||||
this.trigger("session:error", new Error(
|
||||
"Retrieval of session information failed: HTTP " + err));
|
||||
this._handleServerError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -200,6 +199,31 @@ loop.shared.models = (function() {
|
||||
.once("session:ended", this.stopListening, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a loop-server error, which has an optional `errno` property which
|
||||
* is server error identifier.
|
||||
*
|
||||
* Triggers the following events:
|
||||
*
|
||||
* - `session:expired` for expired call urls
|
||||
* - `session:error` for other generic errors
|
||||
*
|
||||
* @param {Error} err Error object.
|
||||
*/
|
||||
_handleServerError: function(err) {
|
||||
switch (err.errno) {
|
||||
// loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
|
||||
// missing OR expired; we treat this information as if the url is always
|
||||
// expired.
|
||||
case 105:
|
||||
this.trigger("session:expired", err);
|
||||
break;
|
||||
default:
|
||||
this.trigger("session:error", err);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears current pending call timer, if any.
|
||||
*/
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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:true, hawk, deriveHawkCredentials */
|
||||
/* global loop:true */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.StandaloneClient = (function($) {
|
||||
@ -46,7 +46,7 @@ loop.StandaloneClient = (function($) {
|
||||
}
|
||||
});
|
||||
|
||||
if (properties.length == 1) {
|
||||
if (properties.length === 1) {
|
||||
return data[properties[0]];
|
||||
}
|
||||
|
||||
@ -62,26 +62,17 @@ loop.StandaloneClient = (function($) {
|
||||
* @param errorThrown See jQuery docs
|
||||
*/
|
||||
_failureHandler: function(cb, jqXHR, textStatus, errorThrown) {
|
||||
var error = "Unknown error.",
|
||||
jsonRes = jqXHR && jqXHR.responseJSON || {};
|
||||
// Received error response format:
|
||||
// { "status": "errors",
|
||||
// "errors": [{
|
||||
// "location": "url",
|
||||
// "name": "token",
|
||||
// "description": "invalid token"
|
||||
// }]}
|
||||
if (jsonRes.status === "errors" && Array.isArray(jsonRes.errors)) {
|
||||
error = "Details: " + jsonRes.errors.map(function(err) {
|
||||
return Object.keys(err).map(function(field) {
|
||||
return field + ": " + err[field];
|
||||
}).join(", ");
|
||||
}).join("; ");
|
||||
}
|
||||
var message = "HTTP " + jqXHR.status + " " + errorThrown +
|
||||
"; " + error;
|
||||
console.error(message);
|
||||
cb(new Error(message));
|
||||
var jsonErr = jqXHR && jqXHR.responseJSON || {};
|
||||
var message = "HTTP " + jqXHR.status + " " + errorThrown;
|
||||
|
||||
// Logging the technical error to the console
|
||||
console.error("Server error", message, jsonErr);
|
||||
|
||||
// Create an error with server error `errno` code attached as a property
|
||||
var err = new Error(message);
|
||||
err.errno = jsonErr.errno;
|
||||
|
||||
cb(err);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1,11 +1,14 @@
|
||||
/** @jsx React.DOM */
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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:true */
|
||||
/* global loop:true, React */
|
||||
/* jshint newcap:false */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.webapp = (function($, _, OT) {
|
||||
loop.webapp = (function($, _, OT, webL10n) {
|
||||
"use strict";
|
||||
|
||||
loop.config = loop.config || {};
|
||||
@ -13,7 +16,8 @@ loop.webapp = (function($, _, OT) {
|
||||
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedViews = loop.shared.views,
|
||||
baseServerUrl = loop.config.serverUrl;
|
||||
baseServerUrl = loop.config.serverUrl,
|
||||
__ = webL10n.get;
|
||||
|
||||
/**
|
||||
* App router.
|
||||
@ -28,6 +32,20 @@ loop.webapp = (function($, _, OT) {
|
||||
template: _.template('<p data-l10n-id="welcome"></p>')
|
||||
});
|
||||
|
||||
/**
|
||||
* Expired call URL view.
|
||||
*/
|
||||
var CallUrlExpiredView = React.createClass({displayName: 'CallUrlExpiredView',
|
||||
render: function() {
|
||||
/* jshint ignore:start */
|
||||
return (
|
||||
// XXX proper UX/design should be implemented here (see bug 1000131)
|
||||
React.DOM.div(null, __("call_url_unavailable_notification"))
|
||||
);
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Conversation launcher view. A ConversationModel is associated and attached
|
||||
* as a `model` property.
|
||||
@ -93,7 +111,7 @@ loop.webapp = (function($, _, OT) {
|
||||
event.preventDefault();
|
||||
this.model.initiate({
|
||||
client: new loop.StandaloneClient({
|
||||
baseServerUrl: baseServerUrl,
|
||||
baseServerUrl: baseServerUrl
|
||||
}),
|
||||
outgoing: true,
|
||||
// For now, we assume both audio and video as there is no
|
||||
@ -112,6 +130,7 @@ loop.webapp = (function($, _, OT) {
|
||||
"": "home",
|
||||
"unsupportedDevice": "unsupportedDevice",
|
||||
"unsupportedBrowser": "unsupportedBrowser",
|
||||
"call/expired": "expired",
|
||||
"call/ongoing/:token": "loadConversation",
|
||||
"call/:token": "initiate"
|
||||
},
|
||||
@ -121,6 +140,12 @@ loop.webapp = (function($, _, OT) {
|
||||
this.loadView(new HomeView());
|
||||
|
||||
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
||||
this.listenTo(this._conversation, "session:expired",
|
||||
this._onSessionExpired);
|
||||
},
|
||||
|
||||
_onSessionExpired: function() {
|
||||
this.navigate("/call/expired", {trigger: true});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -167,6 +192,10 @@ loop.webapp = (function($, _, OT) {
|
||||
this.loadView(new sharedViews.UnsupportedBrowserView());
|
||||
},
|
||||
|
||||
expired: function() {
|
||||
this.loadReactComponent(CallUrlExpiredView());
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads conversation launcher view, setting the received conversation token
|
||||
* to the current conversation model. If a session is currently established,
|
||||
@ -235,10 +264,11 @@ loop.webapp = (function($, _, OT) {
|
||||
|
||||
return {
|
||||
baseServerUrl: baseServerUrl,
|
||||
CallUrlExpiredView: CallUrlExpiredView,
|
||||
ConversationFormView: ConversationFormView,
|
||||
HomeView: HomeView,
|
||||
WebappHelper: WebappHelper,
|
||||
init: init,
|
||||
WebappRouter: WebappRouter
|
||||
};
|
||||
})(jQuery, _, window.OT);
|
||||
})(jQuery, _, window.OT, document.webL10n);
|
||||
|
274
browser/components/loop/standalone/content/js/webapp.jsx
Normal file
274
browser/components/loop/standalone/content/js/webapp.jsx
Normal file
@ -0,0 +1,274 @@
|
||||
/** @jsx React.DOM */
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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:true, React */
|
||||
/* jshint newcap:false */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.webapp = (function($, _, OT, webL10n) {
|
||||
"use strict";
|
||||
|
||||
loop.config = loop.config || {};
|
||||
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
||||
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedViews = loop.shared.views,
|
||||
baseServerUrl = loop.config.serverUrl,
|
||||
__ = webL10n.get;
|
||||
|
||||
/**
|
||||
* App router.
|
||||
* @type {loop.webapp.WebappRouter}
|
||||
*/
|
||||
var router;
|
||||
|
||||
/**
|
||||
* Homepage view.
|
||||
*/
|
||||
var HomeView = sharedViews.BaseView.extend({
|
||||
template: _.template('<p data-l10n-id="welcome"></p>')
|
||||
});
|
||||
|
||||
/**
|
||||
* Expired call URL view.
|
||||
*/
|
||||
var CallUrlExpiredView = React.createClass({
|
||||
render: function() {
|
||||
/* jshint ignore:start */
|
||||
return (
|
||||
// XXX proper UX/design should be implemented here (see bug 1000131)
|
||||
<div>{__("call_url_unavailable_notification")}</div>
|
||||
);
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Conversation launcher view. A ConversationModel is associated and attached
|
||||
* as a `model` property.
|
||||
*/
|
||||
var ConversationFormView = sharedViews.BaseView.extend({
|
||||
template: _.template([
|
||||
'<form>',
|
||||
' <p>',
|
||||
' <button class="btn btn-success" data-l10n-id="start_call"></button>',
|
||||
' </p>',
|
||||
'</form>'
|
||||
].join("")),
|
||||
|
||||
events: {
|
||||
"submit": "initiate"
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Required options:
|
||||
* - {loop.shared.model.ConversationModel} model Conversation model.
|
||||
* - {loop.shared.views.NotificationListView} notifier Notifier component.
|
||||
*
|
||||
* @param {Object} options Options object.
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
|
||||
if (!options.model) {
|
||||
throw new Error("missing required model");
|
||||
}
|
||||
this.model = options.model;
|
||||
|
||||
if (!options.notifier) {
|
||||
throw new Error("missing required notifier");
|
||||
}
|
||||
this.notifier = options.notifier;
|
||||
|
||||
this.listenTo(this.model, "session:error", this._onSessionError);
|
||||
},
|
||||
|
||||
_onSessionError: function(error) {
|
||||
console.error(error);
|
||||
this.notifier.errorL10n("unable_retrieve_call_info");
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables this form to prevent multiple submissions.
|
||||
*
|
||||
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=991126
|
||||
*/
|
||||
disableForm: function() {
|
||||
this.$("button").attr("disabled", "disabled");
|
||||
},
|
||||
|
||||
/**
|
||||
* Initiates the call.
|
||||
*
|
||||
* @param {SubmitEvent} event
|
||||
*/
|
||||
initiate: function(event) {
|
||||
event.preventDefault();
|
||||
this.model.initiate({
|
||||
client: new loop.StandaloneClient({
|
||||
baseServerUrl: baseServerUrl
|
||||
}),
|
||||
outgoing: true,
|
||||
// For now, we assume both audio and video as there is no
|
||||
// other option to select.
|
||||
callType: "audio-video"
|
||||
});
|
||||
this.disableForm();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Webapp Router.
|
||||
*/
|
||||
var WebappRouter = loop.shared.router.BaseConversationRouter.extend({
|
||||
routes: {
|
||||
"": "home",
|
||||
"unsupportedDevice": "unsupportedDevice",
|
||||
"unsupportedBrowser": "unsupportedBrowser",
|
||||
"call/expired": "expired",
|
||||
"call/ongoing/:token": "loadConversation",
|
||||
"call/:token": "initiate"
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
// Load default view
|
||||
this.loadView(new HomeView());
|
||||
|
||||
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
||||
this.listenTo(this._conversation, "session:expired",
|
||||
this._onSessionExpired);
|
||||
},
|
||||
|
||||
_onSessionExpired: function() {
|
||||
this.navigate("/call/expired", {trigger: true});
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {loop.shared.router.BaseConversationRouter.startCall}
|
||||
*/
|
||||
startCall: function() {
|
||||
if (!this._conversation.get("loopToken")) {
|
||||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
this.navigate("call/ongoing/" + this._conversation.get("loopToken"), {
|
||||
trigger: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {loop.shared.router.BaseConversationRouter.endCall}
|
||||
*/
|
||||
endCall: function() {
|
||||
var route = "home";
|
||||
if (this._conversation.get("loopToken")) {
|
||||
route = "call/" + this._conversation.get("loopToken");
|
||||
}
|
||||
this.navigate(route, {trigger: true});
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
this._notifier.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
* Default entry point.
|
||||
*/
|
||||
home: function() {
|
||||
this.loadView(new HomeView());
|
||||
},
|
||||
|
||||
unsupportedDevice: function() {
|
||||
this.loadView(new sharedViews.UnsupportedDeviceView());
|
||||
},
|
||||
|
||||
unsupportedBrowser: function() {
|
||||
this.loadView(new sharedViews.UnsupportedBrowserView());
|
||||
},
|
||||
|
||||
expired: function() {
|
||||
this.loadReactComponent(CallUrlExpiredView());
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads conversation launcher view, setting the received conversation token
|
||||
* to the current conversation model. If a session is currently established,
|
||||
* terminates it first.
|
||||
*
|
||||
* @param {String} loopToken Loop conversation token.
|
||||
*/
|
||||
initiate: function(loopToken) {
|
||||
// Check if a session is ongoing; if so, terminate it
|
||||
if (this._conversation.get("ongoing")) {
|
||||
this._conversation.endSession();
|
||||
}
|
||||
this._conversation.set("loopToken", loopToken);
|
||||
this.loadView(new ConversationFormView({
|
||||
model: this._conversation,
|
||||
notifier: this._notifier
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads conversation establishment view.
|
||||
*
|
||||
*/
|
||||
loadConversation: function(loopToken) {
|
||||
if (!this._conversation.isSessionReady()) {
|
||||
// User has loaded this url directly, actually setup the call.
|
||||
return this.navigate("call/" + loopToken, {trigger: true});
|
||||
}
|
||||
this.loadReactComponent(sharedViews.ConversationView({
|
||||
sdk: OT,
|
||||
model: this._conversation
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Local helpers.
|
||||
*/
|
||||
function WebappHelper() {
|
||||
this._iOSRegex = /^(iPad|iPhone|iPod)/;
|
||||
}
|
||||
|
||||
WebappHelper.prototype.isIOS = function isIOS(platform) {
|
||||
return this._iOSRegex.test(platform);
|
||||
};
|
||||
|
||||
/**
|
||||
* App initialization.
|
||||
*/
|
||||
function init() {
|
||||
var helper = new WebappHelper();
|
||||
router = new WebappRouter({
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"}),
|
||||
conversation: new sharedModels.ConversationModel({}, {
|
||||
sdk: OT,
|
||||
pendingCallTimeout: loop.config.pendingCallTimeout
|
||||
})
|
||||
});
|
||||
Backbone.history.start();
|
||||
if (helper.isIOS(navigator.platform)) {
|
||||
router.navigate("unsupportedDevice", {trigger: true});
|
||||
} else if (!OT.checkSystemRequirements()) {
|
||||
router.navigate("unsupportedBrowser", {trigger: true});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
baseServerUrl: baseServerUrl,
|
||||
CallUrlExpiredView: CallUrlExpiredView,
|
||||
ConversationFormView: ConversationFormView,
|
||||
HomeView: HomeView,
|
||||
WebappHelper: WebappHelper,
|
||||
init: init,
|
||||
WebappRouter: WebappRouter
|
||||
};
|
||||
})(jQuery, _, window.OT, document.webL10n);
|
@ -19,6 +19,7 @@ incompatible_device=Incompatible device
|
||||
sorry_device_unsupported=Sorry, Loop does not currently support your device.
|
||||
use_firefox_windows_mac_linux=Please open this page using the latest Firefox on Windows, Android, Mac or Linux.
|
||||
connection_error_see_console_notification=Call failed; see console for details.
|
||||
call_url_unavailable_notification=This URL is unavailable.
|
||||
|
||||
[fr]
|
||||
call_has_ended=L'appel est terminé.
|
||||
@ -40,3 +41,4 @@ use_latest_firefox.innerHTML=Pour utiliser Loop, merci d'utiliser la dernière v
|
||||
incompatible_device=Plateforme non supportée
|
||||
sorry_device_unsupported=Désolé, Loop ne fonctionne actuellement pas sur votre appareil.
|
||||
use_firefox_windows_mac_linux=Merci d'ouvrir cette page avec une version récente de Firefox pour Windows, Android, Mac ou Linux.
|
||||
call_url_unavailable_notification=Cette URL n'est pas disponible.
|
||||
|
@ -142,18 +142,42 @@ describe("loop.shared.models", function() {
|
||||
sinon.assert.calledWith(conversation.setReady, fakeSessionData);
|
||||
});
|
||||
|
||||
it("should trigger a `session:error` on failure", function(done) {
|
||||
requestCallInfoStub.callsArgWith(2,
|
||||
new Error("failed: HTTP 400 Bad Request; fake"));
|
||||
it("should trigger a `session:error` event errno is undefined",
|
||||
function(done) {
|
||||
var errMsg = "HTTP 500 Server Error; fake";
|
||||
var err = new Error(errMsg);
|
||||
requestCallInfoStub.callsArgWith(2, err);
|
||||
|
||||
conversation.on("session:error", function(err) {
|
||||
expect(err.message).to.match(/failed: HTTP 400 Bad Request; fake/);
|
||||
done();
|
||||
}).initiate({
|
||||
client: fakeClient,
|
||||
outgoing: true
|
||||
conversation.on("session:error", function(err) {
|
||||
expect(err.message).eql(errMsg);
|
||||
done();
|
||||
}).initiate({ client: fakeClient, outgoing: true });
|
||||
});
|
||||
|
||||
it("should trigger a `session:error` event when errno is not 105",
|
||||
function(done) {
|
||||
var errMsg = "HTTP 400 Bad Request; fake";
|
||||
var err = new Error(errMsg);
|
||||
err.errno = 101;
|
||||
requestCallInfoStub.callsArgWith(2, err);
|
||||
|
||||
conversation.on("session:error", function(err) {
|
||||
expect(err.message).eql(errMsg);
|
||||
done();
|
||||
}).initiate({ client: fakeClient, outgoing: true });
|
||||
});
|
||||
|
||||
it("should trigger a `session:expired` event when errno is 105",
|
||||
function(done) {
|
||||
var err = new Error("HTTP 404 Not Found; fake");
|
||||
err.errno = 105;
|
||||
requestCallInfoStub.callsArgWith(2, err);
|
||||
|
||||
conversation.on("session:expired", function(err2) {
|
||||
expect(err2).eql(err);
|
||||
done();
|
||||
}).initiate({ client: fakeClient, outgoing: true });
|
||||
});
|
||||
});
|
||||
|
||||
it("should end the session on outgoing call timeout", function() {
|
||||
requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
|
||||
|
@ -15,15 +15,6 @@ describe("loop.StandaloneClient", function() {
|
||||
callback,
|
||||
fakeToken;
|
||||
|
||||
var fakeErrorRes = JSON.stringify({
|
||||
status: "errors",
|
||||
errors: [{
|
||||
location: "url",
|
||||
name: "token",
|
||||
description: "invalid token"
|
||||
}]
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
||||
@ -50,12 +41,19 @@ describe("loop.StandaloneClient", function() {
|
||||
});
|
||||
|
||||
describe("requestCallInfo", function() {
|
||||
var client;
|
||||
var client, fakeServerErrorDescription;
|
||||
|
||||
beforeEach(function() {
|
||||
client = new loop.StandaloneClient(
|
||||
{baseServerUrl: "http://fake.api"}
|
||||
);
|
||||
fakeServerErrorDescription = {
|
||||
code: 401,
|
||||
errno: 101,
|
||||
error: "error",
|
||||
message: "invalid token",
|
||||
info: "error info"
|
||||
};
|
||||
});
|
||||
|
||||
it("should prevent launching a conversation when token is missing",
|
||||
@ -91,13 +89,26 @@ describe("loop.StandaloneClient", function() {
|
||||
it("should send an error when the request fails", function() {
|
||||
client.requestCallInfo("fake", "audio", callback);
|
||||
|
||||
requests[0].respond(400, {"Content-Type": "application/json"},
|
||||
fakeErrorRes);
|
||||
requests[0].respond(401, {"Content-Type": "application/json"},
|
||||
JSON.stringify(fakeServerErrorDescription));
|
||||
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
|
||||
return /400.*invalid token/.test(err.message);
|
||||
return /HTTP 401 Unauthorized/.test(err.message);
|
||||
}));
|
||||
});
|
||||
|
||||
it("should attach the server error description object to the error " +
|
||||
"passed to the callback",
|
||||
function() {
|
||||
client.requestCallInfo("fake", "audio", callback);
|
||||
|
||||
requests[0].respond(401, {"Content-Type": "application/json"},
|
||||
JSON.stringify(fakeServerErrorDescription));
|
||||
|
||||
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
|
||||
return err.errno === fakeServerErrorDescription.errno;
|
||||
}));
|
||||
});
|
||||
|
||||
it("should send an error if the data is not valid", function() {
|
||||
client.requestCallInfo("fake", "audio", callback);
|
||||
|
||||
|
@ -140,6 +140,19 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#expired", function() {
|
||||
it("should load the CallUrlExpiredView view", function() {
|
||||
router.expired();
|
||||
|
||||
sinon.assert.calledOnce(router.loadReactComponent);
|
||||
sinon.assert.calledWith(router.loadReactComponent,
|
||||
sinon.match(function(value) {
|
||||
return React.addons.TestUtils.isComponentOfType(
|
||||
value, loop.webapp.CallUrlExpiredView);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("#initiate", function() {
|
||||
it("should set the token on the conversation model", function() {
|
||||
router.initiate("fakeToken");
|
||||
@ -251,6 +264,14 @@ describe("loop.webapp", function() {
|
||||
sinon.assert.calledOnce(router.navigate);
|
||||
sinon.assert.calledWithMatch(router.navigate, "call/fakeToken");
|
||||
});
|
||||
|
||||
it("should navigate to call/expired when a session:expired event is " +
|
||||
"received", function() {
|
||||
conversation.trigger("session:expired");
|
||||
|
||||
sinon.assert.calledOnce(router.navigate);
|
||||
sinon.assert.calledWith(router.navigate, "/call/expired");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user