зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1066502 Remove the backbone router from the Loop conversation window, use a react view for control. r=nperriault
This commit is contained in:
Родитель
839ba64784
Коммит
4bb3785445
|
@ -27,13 +27,11 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="loop/shared/js/utils.js"></script>
|
<script type="text/javascript" src="loop/shared/js/utils.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/models.js"></script>
|
<script type="text/javascript" src="loop/shared/js/models.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/router.js"></script>
|
|
||||||
<script type="text/javascript" src="loop/shared/js/mixins.js"></script>
|
<script type="text/javascript" src="loop/shared/js/mixins.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/views.js"></script>
|
<script type="text/javascript" src="loop/shared/js/views.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/feedbackApiClient.js"></script>
|
<script type="text/javascript" src="loop/shared/js/feedbackApiClient.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/websocket.js"></script>
|
<script type="text/javascript" src="loop/shared/js/websocket.js"></script>
|
||||||
<script type="text/javascript" src="loop/js/client.js"></script>
|
<script type="text/javascript" src="loop/js/client.js"></script>
|
||||||
<script type="text/javascript" src="loop/js/desktopRouter.js"></script>
|
|
||||||
<script type="text/javascript" src="loop/js/conversation.js"></script>
|
<script type="text/javascript" src="loop/js/conversation.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -8,16 +8,11 @@
|
||||||
/* global loop:true, React */
|
/* global loop:true, React */
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.conversation = (function(OT, mozL10n) {
|
loop.conversation = (function(mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views,
|
||||||
|
sharedModels = loop.shared.models;
|
||||||
/**
|
|
||||||
* App router.
|
|
||||||
* @type {loop.desktopRouter.DesktopConversationRouter}
|
|
||||||
*/
|
|
||||||
var router;
|
|
||||||
|
|
||||||
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
|
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
|
||||||
|
|
||||||
|
@ -200,92 +195,183 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conversation router.
|
* This view manages the incoming conversation views - from
|
||||||
|
* call initiation through to the actual conversation and call end.
|
||||||
*
|
*
|
||||||
* Required options:
|
* At the moment, it does more than that, these parts need refactoring out.
|
||||||
* - {loop.shared.models.ConversationModel} conversation Conversation model.
|
|
||||||
* - {loop.shared.models.NotificationCollection} notifications
|
|
||||||
*
|
|
||||||
* @type {loop.shared.router.BaseConversationRouter}
|
|
||||||
*/
|
*/
|
||||||
var ConversationRouter = loop.desktopRouter.DesktopConversationRouter.extend({
|
var IncomingConversationView = React.createClass({displayName: 'IncomingConversationView',
|
||||||
routes: {
|
propTypes: {
|
||||||
"incoming/:callId": "incoming",
|
client: React.PropTypes.instanceOf(loop.Client).isRequired,
|
||||||
"call/accept": "accept",
|
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||||
"call/decline": "decline",
|
.isRequired,
|
||||||
"call/ongoing": "conversation",
|
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||||
"call/declineAndBlock": "declineAndBlock",
|
.isRequired,
|
||||||
"call/shutdown": "shutdown",
|
sdk: React.PropTypes.object.isRequired
|
||||||
"call/feedback": "feedback"
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
callStatus: "start"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.conversation.on("accept", this.accept, this);
|
||||||
|
this.props.conversation.on("decline", this.decline, this);
|
||||||
|
this.props.conversation.on("declineAndBlock", this.declineAndBlock, this);
|
||||||
|
this.props.conversation.on("call:accepted", this.accepted, this);
|
||||||
|
this.props.conversation.on("change:publishedStream", this._checkConnected, this);
|
||||||
|
this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
|
||||||
|
this.props.conversation.on("session:ended", this.endCall, this);
|
||||||
|
this.props.conversation.on("session:peer-hungup", this._onPeerHungup, this);
|
||||||
|
this.props.conversation.on("session:network-disconnected", this._onNetworkDisconnected, this);
|
||||||
|
this.props.conversation.on("session:connection-error", this._notifyError, this);
|
||||||
|
|
||||||
|
this.setupIncomingCall();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUnmount: function() {
|
||||||
|
this.props.conversation.off(null, null, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
switch (this.state.callStatus) {
|
||||||
|
case "start": {
|
||||||
|
document.title = mozL10n.get("incoming_call_title2");
|
||||||
|
|
||||||
|
// XXX Don't render anything initially, though this should probably
|
||||||
|
// be some sort of pending view, whilst we connect the websocket.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case "incoming": {
|
||||||
|
document.title = mozL10n.get("incoming_call_title2");
|
||||||
|
|
||||||
|
return (
|
||||||
|
IncomingCallView({
|
||||||
|
model: this.props.conversation,
|
||||||
|
video: this.props.conversation.hasVideoStream("incoming")}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "connected": {
|
||||||
|
// XXX This should be the caller id (bug 1020449)
|
||||||
|
document.title = mozL10n.get("incoming_call_title2");
|
||||||
|
|
||||||
|
var callType = this.props.conversation.get("selectedCallType");
|
||||||
|
|
||||||
|
return (
|
||||||
|
sharedViews.ConversationView({
|
||||||
|
initiate: true,
|
||||||
|
sdk: this.props.sdk,
|
||||||
|
model: this.props.conversation,
|
||||||
|
video: {enabled: callType !== "audio"}}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "end": {
|
||||||
|
document.title = mozL10n.get("conversation_has_ended");
|
||||||
|
|
||||||
|
var feebackAPIBaseUrl = navigator.mozLoop.getLoopCharPref(
|
||||||
|
"feedback.baseUrl");
|
||||||
|
|
||||||
|
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||||
|
|
||||||
|
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
||||||
|
product: navigator.mozLoop.getLoopCharPref("feedback.product"),
|
||||||
|
platform: appVersionInfo.OS,
|
||||||
|
channel: appVersionInfo.channel,
|
||||||
|
version: appVersionInfo.version
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
sharedViews.FeedbackView({
|
||||||
|
feedbackApiClient: feedbackClient,
|
||||||
|
onAfterFeedbackReceived: this.closeWindow.bind(this)}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "close": {
|
||||||
|
window.close();
|
||||||
|
return (React.DOM.div(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override {loop.shared.router.BaseConversationRouter.startCall}
|
* Notify the user that the connection was not possible
|
||||||
|
* @param {{code: number, message: string}} error
|
||||||
*/
|
*/
|
||||||
startCall: function() {
|
_notifyError: function(error) {
|
||||||
this.navigate("call/ongoing", {trigger: true});
|
console.error(error);
|
||||||
|
this.props.notifications.errorL10n("connection_error_see_console_notification");
|
||||||
|
this.setState({callStatus: "end"});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override {loop.shared.router.BaseConversationRouter.endCall}
|
* Peer hung up. Notifies the user and ends the call.
|
||||||
|
*
|
||||||
|
* Event properties:
|
||||||
|
* - {String} connectionId: OT session id
|
||||||
*/
|
*/
|
||||||
endCall: function() {
|
_onPeerHungup: function() {
|
||||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
this.props.notifications.warnL10n("peer_ended_conversation2");
|
||||||
this.navigate("call/feedback", {trigger: true});
|
this.setState({callStatus: "end"});
|
||||||
},
|
},
|
||||||
|
|
||||||
shutdown: function() {
|
/**
|
||||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
* Network disconnected. Notifies the user and ends the call.
|
||||||
|
*/
|
||||||
|
_onNetworkDisconnected: function() {
|
||||||
|
this.props.notifications.warnL10n("network_disconnected");
|
||||||
|
this.setState({callStatus: "end"});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Incoming call route.
|
* Incoming call route.
|
||||||
*
|
|
||||||
* @param {String} callId Identifier assigned by the LoopService
|
|
||||||
* to this incoming call.
|
|
||||||
*/
|
*/
|
||||||
incoming: function(callId) {
|
setupIncomingCall: function() {
|
||||||
navigator.mozLoop.startAlerting();
|
navigator.mozLoop.startAlerting();
|
||||||
this._conversation.once("accept", function() {
|
|
||||||
this.navigate("call/accept", {trigger: true});
|
|
||||||
}.bind(this));
|
|
||||||
this._conversation.once("decline", function() {
|
|
||||||
this.navigate("call/decline", {trigger: true});
|
|
||||||
}.bind(this));
|
|
||||||
this._conversation.once("declineAndBlock", function() {
|
|
||||||
this.navigate("call/declineAndBlock", {trigger: true});
|
|
||||||
}.bind(this));
|
|
||||||
this._conversation.once("call:incoming", this.startCall, this);
|
|
||||||
this._conversation.once("change:publishedStream", this._checkConnected, this);
|
|
||||||
this._conversation.once("change:subscribedStream", this._checkConnected, this);
|
|
||||||
|
|
||||||
var callData = navigator.mozLoop.getCallData(callId);
|
var callData = navigator.mozLoop.getCallData(this.props.conversation.get("callId"));
|
||||||
if (!callData) {
|
if (!callData) {
|
||||||
console.error("Failed to get the call data");
|
console.error("Failed to get the call data");
|
||||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||||
// this by better "call failed" UI.
|
// this by better "call failed" UI.
|
||||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
this.props.notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._conversation.setIncomingSessionData(callData);
|
this.props.conversation.setIncomingSessionData(callData);
|
||||||
this._setupWebSocketAndCallView();
|
this._setupWebSocket();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the actual conversation
|
||||||
|
*/
|
||||||
|
accepted: function() {
|
||||||
|
this.setState({callStatus: "connected"});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the call to the end state
|
||||||
|
*/
|
||||||
|
endCall: function() {
|
||||||
|
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
|
||||||
|
this.setState({callStatus: "end"});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to set up the web socket connection and navigate to the
|
* Used to set up the web socket connection and navigate to the
|
||||||
* call view if appropriate.
|
* call view if appropriate.
|
||||||
*/
|
*/
|
||||||
_setupWebSocketAndCallView: function() {
|
_setupWebSocket: function() {
|
||||||
this._websocket = new loop.CallConnectionWebSocket({
|
this._websocket = new loop.CallConnectionWebSocket({
|
||||||
url: this._conversation.get("progressURL"),
|
url: this.props.conversation.get("progressURL"),
|
||||||
websocketToken: this._conversation.get("websocketToken"),
|
websocketToken: this.props.conversation.get("websocketToken"),
|
||||||
callId: this._conversation.get("callId"),
|
callId: this.props.conversation.get("callId"),
|
||||||
});
|
});
|
||||||
this._websocket.promiseConnect().then(function() {
|
this._websocket.promiseConnect().then(function() {
|
||||||
this.loadReactComponent(loop.conversation.IncomingCallView({
|
this.setState({callStatus: "incoming"});
|
||||||
model: this._conversation,
|
|
||||||
video: this._conversation.hasVideoStream("incoming")
|
|
||||||
}));
|
|
||||||
}.bind(this), function() {
|
}.bind(this), function() {
|
||||||
this._handleSessionError();
|
this._handleSessionError();
|
||||||
return;
|
return;
|
||||||
|
@ -301,7 +387,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
_checkConnected: function() {
|
_checkConnected: function() {
|
||||||
// Check we've had both local and remote streams connected before
|
// Check we've had both local and remote streams connected before
|
||||||
// sending the media up message.
|
// sending the media up message.
|
||||||
if (this._conversation.streamsConnected()) {
|
if (this.props.conversation.streamsConnected()) {
|
||||||
this._websocket.mediaUp();
|
this._websocket.mediaUp();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -337,6 +423,12 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
_abortIncomingCall: function() {
|
_abortIncomingCall: function() {
|
||||||
navigator.mozLoop.stopAlerting();
|
navigator.mozLoop.stopAlerting();
|
||||||
this._websocket.close();
|
this._websocket.close();
|
||||||
|
// Having a timeout here lets the logging for the websocket complete and be
|
||||||
|
// displayed on the console if both are on.
|
||||||
|
setTimeout(this.closeWindow, 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
closeWindow: function() {
|
||||||
window.close();
|
window.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -346,7 +438,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
accept: function() {
|
accept: function() {
|
||||||
navigator.mozLoop.stopAlerting();
|
navigator.mozLoop.stopAlerting();
|
||||||
this._websocket.accept();
|
this._websocket.accept();
|
||||||
this._conversation.incoming();
|
this.props.conversation.accepted();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -354,13 +446,11 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
*/
|
*/
|
||||||
_declineCall: function() {
|
_declineCall: function() {
|
||||||
this._websocket.decline();
|
this._websocket.decline();
|
||||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
|
||||||
// XXX Don't close the window straight away, but let any sends happen
|
this._websocket.close();
|
||||||
// first. Ideally we'd wait to close the window until after we have a
|
// Having a timeout here lets the logging for the websocket complete and be
|
||||||
// response from the server, to know that everything has completed
|
// displayed on the console if both are on.
|
||||||
// successfully. However, that's quite difficult to ensure at the
|
setTimeout(this.closeWindow, 0);
|
||||||
// moment so we'll add it later.
|
|
||||||
setTimeout(window.close, 0);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -379,8 +469,8 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
*/
|
*/
|
||||||
declineAndBlock: function() {
|
declineAndBlock: function() {
|
||||||
navigator.mozLoop.stopAlerting();
|
navigator.mozLoop.stopAlerting();
|
||||||
var token = this._conversation.get("callToken");
|
var token = this.props.conversation.get("callToken");
|
||||||
this._client.deleteCallUrl(token, function(error) {
|
this.props.client.deleteCallUrl(token, function(error) {
|
||||||
// XXX The conversation window will be closed when this cb is triggered
|
// XXX The conversation window will be closed when this cb is triggered
|
||||||
// figure out if there is a better way to report the error to the user
|
// figure out if there is a better way to report the error to the user
|
||||||
// (bug 1048909).
|
// (bug 1048909).
|
||||||
|
@ -389,62 +479,14 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
this._declineCall();
|
this._declineCall();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* conversation is the route when the conversation is active. The start
|
|
||||||
* route should be navigated to first.
|
|
||||||
*/
|
|
||||||
conversation: function() {
|
|
||||||
if (!this._conversation.isSessionReady()) {
|
|
||||||
console.error("Error: navigated to conversation route without " +
|
|
||||||
"the start route to initialise the call first");
|
|
||||||
this._handleSessionError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var callType = this._conversation.get("selectedCallType");
|
|
||||||
var videoStream = callType === "audio" ? false : true;
|
|
||||||
|
|
||||||
/*jshint newcap:false*/
|
|
||||||
this.loadReactComponent(sharedViews.ConversationView({
|
|
||||||
initiate: true,
|
|
||||||
sdk: OT,
|
|
||||||
model: this._conversation,
|
|
||||||
video: {enabled: videoStream}
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a error starting the session
|
* Handles a error starting the session
|
||||||
*/
|
*/
|
||||||
_handleSessionError: function() {
|
_handleSessionError: function() {
|
||||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||||
// this by better "call failed" UI.
|
// this by better "call failed" UI.
|
||||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
this.props.notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Call has ended, display a feedback form.
|
|
||||||
*/
|
|
||||||
feedback: function() {
|
|
||||||
document.title = mozL10n.get("conversation_has_ended");
|
|
||||||
|
|
||||||
var feebackAPIBaseUrl = navigator.mozLoop.getLoopCharPref(
|
|
||||||
"feedback.baseUrl");
|
|
||||||
|
|
||||||
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
|
||||||
|
|
||||||
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
|
||||||
product: navigator.mozLoop.getLoopCharPref("feedback.product"),
|
|
||||||
platform: appVersionInfo.OS,
|
|
||||||
channel: appVersionInfo.channel,
|
|
||||||
version: appVersionInfo.version
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loadReactComponent(sharedViews.FeedbackView({
|
|
||||||
feedbackApiClient: feedbackClient,
|
|
||||||
onAfterFeedbackReceived: window.close.bind(window)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -457,44 +499,50 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
|
|
||||||
// Plug in an alternate client ID mechanism, as localStorage and cookies
|
// Plug in an alternate client ID mechanism, as localStorage and cookies
|
||||||
// don't work in the conversation window
|
// don't work in the conversation window
|
||||||
if (OT && OT.hasOwnProperty("overrideGuidStorage")) {
|
window.OT.overrideGuidStorage({
|
||||||
OT.overrideGuidStorage({
|
get: function(callback) {
|
||||||
get: function(callback) {
|
callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
|
||||||
callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
|
},
|
||||||
},
|
set: function(guid, callback) {
|
||||||
set: function(guid, callback) {
|
navigator.mozLoop.setLoopCharPref("ot.guid", guid);
|
||||||
navigator.mozLoop.setLoopCharPref("ot.guid", guid);
|
callback(null);
|
||||||
callback(null);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.title = mozL10n.get("incoming_call_title2");
|
|
||||||
|
|
||||||
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
||||||
|
|
||||||
var client = new loop.Client();
|
var client = new loop.Client();
|
||||||
router = new ConversationRouter({
|
var conversation = new sharedModels.ConversationModel(
|
||||||
client: client,
|
{}, // Model attributes
|
||||||
conversation: new loop.shared.models.ConversationModel(
|
{sdk: window.OT} // Model dependencies
|
||||||
{}, // Model attributes
|
);
|
||||||
{sdk: OT}), // Model dependencies
|
var notifications = new sharedModels.NotificationCollection();
|
||||||
notifications: new loop.shared.models.NotificationCollection()
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("unload", function(event) {
|
window.addEventListener("unload", function(event) {
|
||||||
// Handle direct close of dialog box via [x] control.
|
// Handle direct close of dialog box via [x] control.
|
||||||
navigator.mozLoop.releaseCallData(router._conversation.get("callId"));
|
navigator.mozLoop.releaseCallData(conversation.get("callId"));
|
||||||
});
|
});
|
||||||
|
|
||||||
Backbone.history.start();
|
// Obtain the callId and pass it to the conversation
|
||||||
|
var helper = new loop.shared.utils.Helper();
|
||||||
|
var locationHash = helper.locationHash();
|
||||||
|
if (locationHash) {
|
||||||
|
conversation.set("callId", locationHash.match(/\#incoming\/(.*)/)[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
React.renderComponent(IncomingConversationView({
|
||||||
|
client: client,
|
||||||
|
conversation: conversation,
|
||||||
|
notifications: notifications,
|
||||||
|
sdk: window.OT}
|
||||||
|
), document.querySelector('#main'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ConversationRouter: ConversationRouter,
|
IncomingConversationView: IncomingConversationView,
|
||||||
IncomingCallView: IncomingCallView,
|
IncomingCallView: IncomingCallView,
|
||||||
init: init
|
init: init
|
||||||
};
|
};
|
||||||
})(window.OT, document.mozL10n);
|
})(document.mozL10n);
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', loop.conversation.init);
|
document.addEventListener('DOMContentLoaded', loop.conversation.init);
|
||||||
|
|
|
@ -8,16 +8,11 @@
|
||||||
/* global loop:true, React */
|
/* global loop:true, React */
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.conversation = (function(OT, mozL10n) {
|
loop.conversation = (function(mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views,
|
||||||
|
sharedModels = loop.shared.models;
|
||||||
/**
|
|
||||||
* App router.
|
|
||||||
* @type {loop.desktopRouter.DesktopConversationRouter}
|
|
||||||
*/
|
|
||||||
var router;
|
|
||||||
|
|
||||||
var IncomingCallView = React.createClass({
|
var IncomingCallView = React.createClass({
|
||||||
|
|
||||||
|
@ -200,92 +195,183 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conversation router.
|
* This view manages the incoming conversation views - from
|
||||||
|
* call initiation through to the actual conversation and call end.
|
||||||
*
|
*
|
||||||
* Required options:
|
* At the moment, it does more than that, these parts need refactoring out.
|
||||||
* - {loop.shared.models.ConversationModel} conversation Conversation model.
|
|
||||||
* - {loop.shared.models.NotificationCollection} notifications
|
|
||||||
*
|
|
||||||
* @type {loop.shared.router.BaseConversationRouter}
|
|
||||||
*/
|
*/
|
||||||
var ConversationRouter = loop.desktopRouter.DesktopConversationRouter.extend({
|
var IncomingConversationView = React.createClass({
|
||||||
routes: {
|
propTypes: {
|
||||||
"incoming/:callId": "incoming",
|
client: React.PropTypes.instanceOf(loop.Client).isRequired,
|
||||||
"call/accept": "accept",
|
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||||
"call/decline": "decline",
|
.isRequired,
|
||||||
"call/ongoing": "conversation",
|
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||||
"call/declineAndBlock": "declineAndBlock",
|
.isRequired,
|
||||||
"call/shutdown": "shutdown",
|
sdk: React.PropTypes.object.isRequired
|
||||||
"call/feedback": "feedback"
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
callStatus: "start"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.props.conversation.on("accept", this.accept, this);
|
||||||
|
this.props.conversation.on("decline", this.decline, this);
|
||||||
|
this.props.conversation.on("declineAndBlock", this.declineAndBlock, this);
|
||||||
|
this.props.conversation.on("call:accepted", this.accepted, this);
|
||||||
|
this.props.conversation.on("change:publishedStream", this._checkConnected, this);
|
||||||
|
this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
|
||||||
|
this.props.conversation.on("session:ended", this.endCall, this);
|
||||||
|
this.props.conversation.on("session:peer-hungup", this._onPeerHungup, this);
|
||||||
|
this.props.conversation.on("session:network-disconnected", this._onNetworkDisconnected, this);
|
||||||
|
this.props.conversation.on("session:connection-error", this._notifyError, this);
|
||||||
|
|
||||||
|
this.setupIncomingCall();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUnmount: function() {
|
||||||
|
this.props.conversation.off(null, null, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
switch (this.state.callStatus) {
|
||||||
|
case "start": {
|
||||||
|
document.title = mozL10n.get("incoming_call_title2");
|
||||||
|
|
||||||
|
// XXX Don't render anything initially, though this should probably
|
||||||
|
// be some sort of pending view, whilst we connect the websocket.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case "incoming": {
|
||||||
|
document.title = mozL10n.get("incoming_call_title2");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IncomingCallView
|
||||||
|
model={this.props.conversation}
|
||||||
|
video={this.props.conversation.hasVideoStream("incoming")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "connected": {
|
||||||
|
// XXX This should be the caller id (bug 1020449)
|
||||||
|
document.title = mozL10n.get("incoming_call_title2");
|
||||||
|
|
||||||
|
var callType = this.props.conversation.get("selectedCallType");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<sharedViews.ConversationView
|
||||||
|
initiate={true}
|
||||||
|
sdk={this.props.sdk}
|
||||||
|
model={this.props.conversation}
|
||||||
|
video={{enabled: callType !== "audio"}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "end": {
|
||||||
|
document.title = mozL10n.get("conversation_has_ended");
|
||||||
|
|
||||||
|
var feebackAPIBaseUrl = navigator.mozLoop.getLoopCharPref(
|
||||||
|
"feedback.baseUrl");
|
||||||
|
|
||||||
|
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||||
|
|
||||||
|
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
||||||
|
product: navigator.mozLoop.getLoopCharPref("feedback.product"),
|
||||||
|
platform: appVersionInfo.OS,
|
||||||
|
channel: appVersionInfo.channel,
|
||||||
|
version: appVersionInfo.version
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<sharedViews.FeedbackView
|
||||||
|
feedbackApiClient={feedbackClient}
|
||||||
|
onAfterFeedbackReceived={this.closeWindow.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "close": {
|
||||||
|
window.close();
|
||||||
|
return (<div/>);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override {loop.shared.router.BaseConversationRouter.startCall}
|
* Notify the user that the connection was not possible
|
||||||
|
* @param {{code: number, message: string}} error
|
||||||
*/
|
*/
|
||||||
startCall: function() {
|
_notifyError: function(error) {
|
||||||
this.navigate("call/ongoing", {trigger: true});
|
console.error(error);
|
||||||
|
this.props.notifications.errorL10n("connection_error_see_console_notification");
|
||||||
|
this.setState({callStatus: "end"});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override {loop.shared.router.BaseConversationRouter.endCall}
|
* Peer hung up. Notifies the user and ends the call.
|
||||||
|
*
|
||||||
|
* Event properties:
|
||||||
|
* - {String} connectionId: OT session id
|
||||||
*/
|
*/
|
||||||
endCall: function() {
|
_onPeerHungup: function() {
|
||||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
this.props.notifications.warnL10n("peer_ended_conversation2");
|
||||||
this.navigate("call/feedback", {trigger: true});
|
this.setState({callStatus: "end"});
|
||||||
},
|
},
|
||||||
|
|
||||||
shutdown: function() {
|
/**
|
||||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
* Network disconnected. Notifies the user and ends the call.
|
||||||
|
*/
|
||||||
|
_onNetworkDisconnected: function() {
|
||||||
|
this.props.notifications.warnL10n("network_disconnected");
|
||||||
|
this.setState({callStatus: "end"});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Incoming call route.
|
* Incoming call route.
|
||||||
*
|
|
||||||
* @param {String} callId Identifier assigned by the LoopService
|
|
||||||
* to this incoming call.
|
|
||||||
*/
|
*/
|
||||||
incoming: function(callId) {
|
setupIncomingCall: function() {
|
||||||
navigator.mozLoop.startAlerting();
|
navigator.mozLoop.startAlerting();
|
||||||
this._conversation.once("accept", function() {
|
|
||||||
this.navigate("call/accept", {trigger: true});
|
|
||||||
}.bind(this));
|
|
||||||
this._conversation.once("decline", function() {
|
|
||||||
this.navigate("call/decline", {trigger: true});
|
|
||||||
}.bind(this));
|
|
||||||
this._conversation.once("declineAndBlock", function() {
|
|
||||||
this.navigate("call/declineAndBlock", {trigger: true});
|
|
||||||
}.bind(this));
|
|
||||||
this._conversation.once("call:incoming", this.startCall, this);
|
|
||||||
this._conversation.once("change:publishedStream", this._checkConnected, this);
|
|
||||||
this._conversation.once("change:subscribedStream", this._checkConnected, this);
|
|
||||||
|
|
||||||
var callData = navigator.mozLoop.getCallData(callId);
|
var callData = navigator.mozLoop.getCallData(this.props.conversation.get("callId"));
|
||||||
if (!callData) {
|
if (!callData) {
|
||||||
console.error("Failed to get the call data");
|
console.error("Failed to get the call data");
|
||||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||||
// this by better "call failed" UI.
|
// this by better "call failed" UI.
|
||||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
this.props.notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._conversation.setIncomingSessionData(callData);
|
this.props.conversation.setIncomingSessionData(callData);
|
||||||
this._setupWebSocketAndCallView();
|
this._setupWebSocket();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the actual conversation
|
||||||
|
*/
|
||||||
|
accepted: function() {
|
||||||
|
this.setState({callStatus: "connected"});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the call to the end state
|
||||||
|
*/
|
||||||
|
endCall: function() {
|
||||||
|
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
|
||||||
|
this.setState({callStatus: "end"});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to set up the web socket connection and navigate to the
|
* Used to set up the web socket connection and navigate to the
|
||||||
* call view if appropriate.
|
* call view if appropriate.
|
||||||
*/
|
*/
|
||||||
_setupWebSocketAndCallView: function() {
|
_setupWebSocket: function() {
|
||||||
this._websocket = new loop.CallConnectionWebSocket({
|
this._websocket = new loop.CallConnectionWebSocket({
|
||||||
url: this._conversation.get("progressURL"),
|
url: this.props.conversation.get("progressURL"),
|
||||||
websocketToken: this._conversation.get("websocketToken"),
|
websocketToken: this.props.conversation.get("websocketToken"),
|
||||||
callId: this._conversation.get("callId"),
|
callId: this.props.conversation.get("callId"),
|
||||||
});
|
});
|
||||||
this._websocket.promiseConnect().then(function() {
|
this._websocket.promiseConnect().then(function() {
|
||||||
this.loadReactComponent(loop.conversation.IncomingCallView({
|
this.setState({callStatus: "incoming"});
|
||||||
model: this._conversation,
|
|
||||||
video: this._conversation.hasVideoStream("incoming")
|
|
||||||
}));
|
|
||||||
}.bind(this), function() {
|
}.bind(this), function() {
|
||||||
this._handleSessionError();
|
this._handleSessionError();
|
||||||
return;
|
return;
|
||||||
|
@ -301,7 +387,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
_checkConnected: function() {
|
_checkConnected: function() {
|
||||||
// Check we've had both local and remote streams connected before
|
// Check we've had both local and remote streams connected before
|
||||||
// sending the media up message.
|
// sending the media up message.
|
||||||
if (this._conversation.streamsConnected()) {
|
if (this.props.conversation.streamsConnected()) {
|
||||||
this._websocket.mediaUp();
|
this._websocket.mediaUp();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -337,6 +423,12 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
_abortIncomingCall: function() {
|
_abortIncomingCall: function() {
|
||||||
navigator.mozLoop.stopAlerting();
|
navigator.mozLoop.stopAlerting();
|
||||||
this._websocket.close();
|
this._websocket.close();
|
||||||
|
// Having a timeout here lets the logging for the websocket complete and be
|
||||||
|
// displayed on the console if both are on.
|
||||||
|
setTimeout(this.closeWindow, 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
closeWindow: function() {
|
||||||
window.close();
|
window.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -346,7 +438,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
accept: function() {
|
accept: function() {
|
||||||
navigator.mozLoop.stopAlerting();
|
navigator.mozLoop.stopAlerting();
|
||||||
this._websocket.accept();
|
this._websocket.accept();
|
||||||
this._conversation.incoming();
|
this.props.conversation.accepted();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -354,13 +446,11 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
*/
|
*/
|
||||||
_declineCall: function() {
|
_declineCall: function() {
|
||||||
this._websocket.decline();
|
this._websocket.decline();
|
||||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
|
||||||
// XXX Don't close the window straight away, but let any sends happen
|
this._websocket.close();
|
||||||
// first. Ideally we'd wait to close the window until after we have a
|
// Having a timeout here lets the logging for the websocket complete and be
|
||||||
// response from the server, to know that everything has completed
|
// displayed on the console if both are on.
|
||||||
// successfully. However, that's quite difficult to ensure at the
|
setTimeout(this.closeWindow, 0);
|
||||||
// moment so we'll add it later.
|
|
||||||
setTimeout(window.close, 0);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -379,8 +469,8 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
*/
|
*/
|
||||||
declineAndBlock: function() {
|
declineAndBlock: function() {
|
||||||
navigator.mozLoop.stopAlerting();
|
navigator.mozLoop.stopAlerting();
|
||||||
var token = this._conversation.get("callToken");
|
var token = this.props.conversation.get("callToken");
|
||||||
this._client.deleteCallUrl(token, function(error) {
|
this.props.client.deleteCallUrl(token, function(error) {
|
||||||
// XXX The conversation window will be closed when this cb is triggered
|
// XXX The conversation window will be closed when this cb is triggered
|
||||||
// figure out if there is a better way to report the error to the user
|
// figure out if there is a better way to report the error to the user
|
||||||
// (bug 1048909).
|
// (bug 1048909).
|
||||||
|
@ -389,62 +479,14 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
this._declineCall();
|
this._declineCall();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* conversation is the route when the conversation is active. The start
|
|
||||||
* route should be navigated to first.
|
|
||||||
*/
|
|
||||||
conversation: function() {
|
|
||||||
if (!this._conversation.isSessionReady()) {
|
|
||||||
console.error("Error: navigated to conversation route without " +
|
|
||||||
"the start route to initialise the call first");
|
|
||||||
this._handleSessionError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var callType = this._conversation.get("selectedCallType");
|
|
||||||
var videoStream = callType === "audio" ? false : true;
|
|
||||||
|
|
||||||
/*jshint newcap:false*/
|
|
||||||
this.loadReactComponent(sharedViews.ConversationView({
|
|
||||||
initiate: true,
|
|
||||||
sdk: OT,
|
|
||||||
model: this._conversation,
|
|
||||||
video: {enabled: videoStream}
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a error starting the session
|
* Handles a error starting the session
|
||||||
*/
|
*/
|
||||||
_handleSessionError: function() {
|
_handleSessionError: function() {
|
||||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||||
// this by better "call failed" UI.
|
// this by better "call failed" UI.
|
||||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
this.props.notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Call has ended, display a feedback form.
|
|
||||||
*/
|
|
||||||
feedback: function() {
|
|
||||||
document.title = mozL10n.get("conversation_has_ended");
|
|
||||||
|
|
||||||
var feebackAPIBaseUrl = navigator.mozLoop.getLoopCharPref(
|
|
||||||
"feedback.baseUrl");
|
|
||||||
|
|
||||||
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
|
||||||
|
|
||||||
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
|
||||||
product: navigator.mozLoop.getLoopCharPref("feedback.product"),
|
|
||||||
platform: appVersionInfo.OS,
|
|
||||||
channel: appVersionInfo.channel,
|
|
||||||
version: appVersionInfo.version
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loadReactComponent(sharedViews.FeedbackView({
|
|
||||||
feedbackApiClient: feedbackClient,
|
|
||||||
onAfterFeedbackReceived: window.close.bind(window)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -457,44 +499,50 @@ loop.conversation = (function(OT, mozL10n) {
|
||||||
|
|
||||||
// Plug in an alternate client ID mechanism, as localStorage and cookies
|
// Plug in an alternate client ID mechanism, as localStorage and cookies
|
||||||
// don't work in the conversation window
|
// don't work in the conversation window
|
||||||
if (OT && OT.hasOwnProperty("overrideGuidStorage")) {
|
window.OT.overrideGuidStorage({
|
||||||
OT.overrideGuidStorage({
|
get: function(callback) {
|
||||||
get: function(callback) {
|
callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
|
||||||
callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
|
},
|
||||||
},
|
set: function(guid, callback) {
|
||||||
set: function(guid, callback) {
|
navigator.mozLoop.setLoopCharPref("ot.guid", guid);
|
||||||
navigator.mozLoop.setLoopCharPref("ot.guid", guid);
|
callback(null);
|
||||||
callback(null);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.title = mozL10n.get("incoming_call_title2");
|
|
||||||
|
|
||||||
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
||||||
|
|
||||||
var client = new loop.Client();
|
var client = new loop.Client();
|
||||||
router = new ConversationRouter({
|
var conversation = new sharedModels.ConversationModel(
|
||||||
client: client,
|
{}, // Model attributes
|
||||||
conversation: new loop.shared.models.ConversationModel(
|
{sdk: window.OT} // Model dependencies
|
||||||
{}, // Model attributes
|
);
|
||||||
{sdk: OT}), // Model dependencies
|
var notifications = new sharedModels.NotificationCollection();
|
||||||
notifications: new loop.shared.models.NotificationCollection()
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("unload", function(event) {
|
window.addEventListener("unload", function(event) {
|
||||||
// Handle direct close of dialog box via [x] control.
|
// Handle direct close of dialog box via [x] control.
|
||||||
navigator.mozLoop.releaseCallData(router._conversation.get("callId"));
|
navigator.mozLoop.releaseCallData(conversation.get("callId"));
|
||||||
});
|
});
|
||||||
|
|
||||||
Backbone.history.start();
|
// Obtain the callId and pass it to the conversation
|
||||||
|
var helper = new loop.shared.utils.Helper();
|
||||||
|
var locationHash = helper.locationHash();
|
||||||
|
if (locationHash) {
|
||||||
|
conversation.set("callId", locationHash.match(/\#incoming\/(.*)/)[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
React.renderComponent(<IncomingConversationView
|
||||||
|
client={client}
|
||||||
|
conversation={conversation}
|
||||||
|
notifications={notifications}
|
||||||
|
sdk={window.OT}
|
||||||
|
/>, document.querySelector('#main'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ConversationRouter: ConversationRouter,
|
IncomingConversationView: IncomingConversationView,
|
||||||
IncomingCallView: IncomingCallView,
|
IncomingCallView: IncomingCallView,
|
||||||
init: init
|
init: init
|
||||||
};
|
};
|
||||||
})(window.OT, document.mozL10n);
|
})(document.mozL10n);
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', loop.conversation.init);
|
document.addEventListener('DOMContentLoaded', loop.conversation.init);
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
/* jshint esnext:true */
|
|
||||||
/* global loop:true */
|
|
||||||
|
|
||||||
var loop = loop || {};
|
|
||||||
loop.desktopRouter = (function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On the desktop app, the use of about: uris prevents us from changing the
|
|
||||||
* url of the location. As a result, we change the navigate function to simply
|
|
||||||
* activate the new routes, and not try changing the url.
|
|
||||||
*
|
|
||||||
* XXX It is conceivable we might be able to remove this in future, if we
|
|
||||||
* can either swap to resource uris or remove the limitation on the about uris.
|
|
||||||
*/
|
|
||||||
var extendedRouter = {
|
|
||||||
navigate: function(to) {
|
|
||||||
this[this.routes[to]]();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var DesktopRouter = loop.shared.router.BaseRouter.extend(extendedRouter);
|
|
||||||
|
|
||||||
var DesktopConversationRouter =
|
|
||||||
loop.shared.router.BaseConversationRouter.extend(extendedRouter);
|
|
||||||
|
|
||||||
return {
|
|
||||||
DesktopRouter: DesktopRouter,
|
|
||||||
DesktopConversationRouter: DesktopConversationRouter
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -17,12 +17,6 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var ContactsList = loop.contacts.ContactsList;
|
var ContactsList = loop.contacts.ContactsList;
|
||||||
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
||||||
|
|
||||||
/**
|
|
||||||
* Panel router.
|
|
||||||
* @type {loop.desktopRouter.DesktopRouter}
|
|
||||||
*/
|
|
||||||
var router;
|
|
||||||
|
|
||||||
var TabView = React.createClass({displayName: 'TabView',
|
var TabView = React.createClass({displayName: 'TabView',
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -17,12 +17,6 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var ContactsList = loop.contacts.ContactsList;
|
var ContactsList = loop.contacts.ContactsList;
|
||||||
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
||||||
|
|
||||||
/**
|
|
||||||
* Panel router.
|
|
||||||
* @type {loop.desktopRouter.DesktopRouter}
|
|
||||||
*/
|
|
||||||
var router;
|
|
||||||
|
|
||||||
var TabView = React.createClass({
|
var TabView = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -77,10 +77,10 @@ loop.shared.models = (function(l10n) {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts an incoming conversation.
|
* Indicates an incoming conversation has been accepted.
|
||||||
*/
|
*/
|
||||||
incoming: function() {
|
accepted: function() {
|
||||||
this.trigger("call:incoming");
|
this.trigger("call:accepted");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
/* 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 */
|
|
||||||
|
|
||||||
var loop = loop || {};
|
|
||||||
loop.shared = loop.shared || {};
|
|
||||||
loop.shared.router = (function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base Router. Allows defining a main active view and ease toggling it when
|
|
||||||
* the active route changes.
|
|
||||||
*
|
|
||||||
* @link http://mikeygee.com/blog/backbone.html
|
|
||||||
*/
|
|
||||||
var BaseRouter = Backbone.Router.extend({
|
|
||||||
/**
|
|
||||||
* Notifications collection.
|
|
||||||
* @type {loop.shared.models.NotificationCollection}
|
|
||||||
*/
|
|
||||||
_notifications: undefined,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* Required options:
|
|
||||||
* - {loop.shared.models.NotificationCollection} notifications
|
|
||||||
*
|
|
||||||
* @param {Object} options Options object.
|
|
||||||
*/
|
|
||||||
constructor: function(options) {
|
|
||||||
options = options || {};
|
|
||||||
if (!options.notifications) {
|
|
||||||
throw new Error("missing required notifications");
|
|
||||||
}
|
|
||||||
this._notifications = options.notifications;
|
|
||||||
|
|
||||||
Backbone.Router.apply(this, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a React component as current active view.
|
|
||||||
*
|
|
||||||
* @param {React} reactComponent React component.
|
|
||||||
*/
|
|
||||||
loadReactComponent: function(reactComponent) {
|
|
||||||
this.clearActiveView();
|
|
||||||
React.renderComponent(reactComponent,
|
|
||||||
document.querySelector("#main"));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears current active view.
|
|
||||||
*/
|
|
||||||
clearActiveView: function() {
|
|
||||||
React.unmountComponentAtNode(document.querySelector("#main"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base conversation router, implementing common behaviors when handling
|
|
||||||
* a conversation.
|
|
||||||
*/
|
|
||||||
var BaseConversationRouter = BaseRouter.extend({
|
|
||||||
/**
|
|
||||||
* Current conversation.
|
|
||||||
* @type {loop.shared.models.ConversationModel}
|
|
||||||
*/
|
|
||||||
_conversation: undefined,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor. Defining it as `constructor` allows implementing an
|
|
||||||
* `initialize` method in child classes without needing calling this parent
|
|
||||||
* one. See http://backbonejs.org/#Model-constructor (same for Router)
|
|
||||||
*
|
|
||||||
* Required options:
|
|
||||||
* - {loop.shared.model.ConversationModel} model Conversation model.
|
|
||||||
*
|
|
||||||
* @param {Object} options Options object.
|
|
||||||
*/
|
|
||||||
constructor: function(options) {
|
|
||||||
options = options || {};
|
|
||||||
if (!options.conversation) {
|
|
||||||
throw new Error("missing required conversation");
|
|
||||||
}
|
|
||||||
if (!options.client) {
|
|
||||||
throw new Error("missing required client");
|
|
||||||
}
|
|
||||||
this._conversation = options.conversation;
|
|
||||||
this._client = options.client;
|
|
||||||
|
|
||||||
this.listenTo(this._conversation, "session:ended", this._onSessionEnded);
|
|
||||||
this.listenTo(this._conversation, "session:peer-hungup",
|
|
||||||
this._onPeerHungup);
|
|
||||||
this.listenTo(this._conversation, "session:network-disconnected",
|
|
||||||
this._onNetworkDisconnected);
|
|
||||||
this.listenTo(this._conversation, "session:connection-error",
|
|
||||||
this._notifyError);
|
|
||||||
|
|
||||||
BaseRouter.apply(this, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify the user that the connection was not possible
|
|
||||||
* @param {{code: number, message: string}} error
|
|
||||||
*/
|
|
||||||
_notifyError: function(error) {
|
|
||||||
console.log(error);
|
|
||||||
this._notifications.errorL10n("connection_error_see_console_notification");
|
|
||||||
this.endCall();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ends the call. This method should be overriden.
|
|
||||||
*/
|
|
||||||
endCall: function() {},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Session has ended. Notifies the user and ends the call.
|
|
||||||
*/
|
|
||||||
_onSessionEnded: function() {
|
|
||||||
this.endCall();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Peer hung up. Notifies the user and ends the call.
|
|
||||||
*
|
|
||||||
* Event properties:
|
|
||||||
* - {String} connectionId: OT session id
|
|
||||||
*
|
|
||||||
* @param {Object} event
|
|
||||||
*/
|
|
||||||
_onPeerHungup: function() {
|
|
||||||
this._notifications.warnL10n("peer_ended_conversation2");
|
|
||||||
this.endCall();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Network disconnected. Notifies the user and ends the call.
|
|
||||||
*/
|
|
||||||
_onNetworkDisconnected: function() {
|
|
||||||
this._notifications.warnL10n("network_disconnected");
|
|
||||||
this.endCall();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
BaseRouter: BaseRouter,
|
|
||||||
BaseConversationRouter: BaseConversationRouter
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -46,7 +46,29 @@ loop.shared.utils = (function() {
|
||||||
return !!localStorage.getItem(prefName);
|
return !!localStorage.getItem(prefName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for general things
|
||||||
|
*/
|
||||||
|
function Helper() {
|
||||||
|
this._iOSRegex = /^(iPad|iPhone|iPod)/;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper.prototype = {
|
||||||
|
isFirefox: function(platform) {
|
||||||
|
return platform.indexOf("Firefox") !== -1;
|
||||||
|
},
|
||||||
|
|
||||||
|
isIOS: function(platform) {
|
||||||
|
return this._iOSRegex.test(platform);
|
||||||
|
},
|
||||||
|
|
||||||
|
locationHash: function() {
|
||||||
|
return window.location.hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
Helper: Helper,
|
||||||
getTargetPlatform: getTargetPlatform,
|
getTargetPlatform: getTargetPlatform,
|
||||||
getBoolPreference: getBoolPreference
|
getBoolPreference: getBoolPreference
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,6 @@ browser.jar:
|
||||||
|
|
||||||
# Desktop script
|
# Desktop script
|
||||||
content/browser/loop/js/client.js (content/js/client.js)
|
content/browser/loop/js/client.js (content/js/client.js)
|
||||||
content/browser/loop/js/desktopRouter.js (content/js/desktopRouter.js)
|
|
||||||
content/browser/loop/js/conversation.js (content/js/conversation.js)
|
content/browser/loop/js/conversation.js (content/js/conversation.js)
|
||||||
content/browser/loop/js/otconfig.js (content/js/otconfig.js)
|
content/browser/loop/js/otconfig.js (content/js/otconfig.js)
|
||||||
content/browser/loop/js/panel.js (content/js/panel.js)
|
content/browser/loop/js/panel.js (content/js/panel.js)
|
||||||
|
@ -55,7 +54,6 @@ browser.jar:
|
||||||
# Shared scripts
|
# Shared scripts
|
||||||
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
|
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
|
||||||
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
||||||
content/browser/loop/shared/js/router.js (content/shared/js/router.js)
|
|
||||||
content/browser/loop/shared/js/mixins.js (content/shared/js/mixins.js)
|
content/browser/loop/shared/js/mixins.js (content/shared/js/mixins.js)
|
||||||
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
|
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
|
||||||
content/browser/loop/shared/js/utils.js (content/shared/js/utils.js)
|
content/browser/loop/shared/js/utils.js (content/shared/js/utils.js)
|
||||||
|
|
|
@ -15,7 +15,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
||||||
|
|
||||||
var sharedModels = loop.shared.models,
|
var sharedModels = loop.shared.models,
|
||||||
sharedViews = loop.shared.views;
|
sharedViews = loop.shared.views,
|
||||||
|
sharedUtils = loop.shared.utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Homepage view.
|
* Homepage view.
|
||||||
|
@ -435,7 +436,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
||||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||||
.isRequired,
|
.isRequired,
|
||||||
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
|
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
|
||||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||||
.isRequired,
|
.isRequired,
|
||||||
sdk: React.PropTypes.object.isRequired,
|
sdk: React.PropTypes.object.isRequired,
|
||||||
|
@ -690,7 +691,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
||||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||||
.isRequired,
|
.isRequired,
|
||||||
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
|
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
|
||||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||||
.isRequired,
|
.isRequired,
|
||||||
sdk: React.PropTypes.object.isRequired,
|
sdk: React.PropTypes.object.isRequired,
|
||||||
|
@ -726,32 +727,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Local helpers.
|
|
||||||
*/
|
|
||||||
function WebappHelper() {
|
|
||||||
this._iOSRegex = /^(iPad|iPhone|iPod)/;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebappHelper.prototype = {
|
|
||||||
isFirefox: function(platform) {
|
|
||||||
return platform.indexOf("Firefox") !== -1;
|
|
||||||
},
|
|
||||||
|
|
||||||
isIOS: function(platform) {
|
|
||||||
return this._iOSRegex.test(platform);
|
|
||||||
},
|
|
||||||
|
|
||||||
locationHash: function() {
|
|
||||||
return window.location.hash;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App initialization.
|
* App initialization.
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
var helper = new WebappHelper();
|
var helper = new sharedUtils.Helper();
|
||||||
var client = new loop.StandaloneClient({
|
var client = new loop.StandaloneClient({
|
||||||
baseServerUrl: loop.config.serverUrl
|
baseServerUrl: loop.config.serverUrl
|
||||||
});
|
});
|
||||||
|
@ -797,7 +777,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
UnsupportedDeviceView: UnsupportedDeviceView,
|
UnsupportedDeviceView: UnsupportedDeviceView,
|
||||||
init: init,
|
init: init,
|
||||||
PromoteFirefoxView: PromoteFirefoxView,
|
PromoteFirefoxView: PromoteFirefoxView,
|
||||||
WebappHelper: WebappHelper,
|
|
||||||
WebappRootView: WebappRootView
|
WebappRootView: WebappRootView
|
||||||
};
|
};
|
||||||
})(jQuery, _, window.OT, navigator.mozL10n);
|
})(jQuery, _, window.OT, navigator.mozL10n);
|
||||||
|
|
|
@ -15,7 +15,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
||||||
|
|
||||||
var sharedModels = loop.shared.models,
|
var sharedModels = loop.shared.models,
|
||||||
sharedViews = loop.shared.views;
|
sharedViews = loop.shared.views,
|
||||||
|
sharedUtils = loop.shared.utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Homepage view.
|
* Homepage view.
|
||||||
|
@ -435,7 +436,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
||||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||||
.isRequired,
|
.isRequired,
|
||||||
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
|
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
|
||||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||||
.isRequired,
|
.isRequired,
|
||||||
sdk: React.PropTypes.object.isRequired,
|
sdk: React.PropTypes.object.isRequired,
|
||||||
|
@ -690,7 +691,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
||||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||||
.isRequired,
|
.isRequired,
|
||||||
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
|
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
|
||||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||||
.isRequired,
|
.isRequired,
|
||||||
sdk: React.PropTypes.object.isRequired,
|
sdk: React.PropTypes.object.isRequired,
|
||||||
|
@ -726,32 +727,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Local helpers.
|
|
||||||
*/
|
|
||||||
function WebappHelper() {
|
|
||||||
this._iOSRegex = /^(iPad|iPhone|iPod)/;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebappHelper.prototype = {
|
|
||||||
isFirefox: function(platform) {
|
|
||||||
return platform.indexOf("Firefox") !== -1;
|
|
||||||
},
|
|
||||||
|
|
||||||
isIOS: function(platform) {
|
|
||||||
return this._iOSRegex.test(platform);
|
|
||||||
},
|
|
||||||
|
|
||||||
locationHash: function() {
|
|
||||||
return window.location.hash;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App initialization.
|
* App initialization.
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
var helper = new WebappHelper();
|
var helper = new sharedUtils.Helper();
|
||||||
var client = new loop.StandaloneClient({
|
var client = new loop.StandaloneClient({
|
||||||
baseServerUrl: loop.config.serverUrl
|
baseServerUrl: loop.config.serverUrl
|
||||||
});
|
});
|
||||||
|
@ -797,7 +777,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||||
UnsupportedDeviceView: UnsupportedDeviceView,
|
UnsupportedDeviceView: UnsupportedDeviceView,
|
||||||
init: init,
|
init: init,
|
||||||
PromoteFirefoxView: PromoteFirefoxView,
|
PromoteFirefoxView: PromoteFirefoxView,
|
||||||
WebappHelper: WebappHelper,
|
|
||||||
WebappRootView: WebappRootView
|
WebappRootView: WebappRootView
|
||||||
};
|
};
|
||||||
})(jQuery, _, window.OT, navigator.mozL10n);
|
})(jQuery, _, window.OT, navigator.mozL10n);
|
||||||
|
|
|
@ -9,10 +9,24 @@ var expect = chai.expect;
|
||||||
describe("loop.conversation", function() {
|
describe("loop.conversation", function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ConversationRouter = loop.conversation.ConversationRouter,
|
var sharedModels = loop.shared.models,
|
||||||
|
sharedView = loop.shared.views,
|
||||||
sandbox,
|
sandbox,
|
||||||
notifications;
|
notifications;
|
||||||
|
|
||||||
|
// XXX refactor to Just Work with "sandbox.stubComponent" or else
|
||||||
|
// just pass in the sandbox and put somewhere generally usable
|
||||||
|
|
||||||
|
function stubComponent(obj, component, mockTagName){
|
||||||
|
var reactClass = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
var mockTagName = mockTagName || "div";
|
||||||
|
return React.DOM[mockTagName](null, this.props.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sandbox.stub(obj, component, reactClass);
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = sinon.sandbox.create();
|
sandbox = sinon.sandbox.create();
|
||||||
sandbox.useFakeTimers();
|
sandbox.useFakeTimers();
|
||||||
|
@ -26,14 +40,14 @@ describe("loop.conversation", function() {
|
||||||
get locale() {
|
get locale() {
|
||||||
return "en-US";
|
return "en-US";
|
||||||
},
|
},
|
||||||
setLoopCharPref: sandbox.stub(),
|
setLoopCharPref: sinon.stub(),
|
||||||
getLoopCharPref: sandbox.stub(),
|
getLoopCharPref: sinon.stub(),
|
||||||
getLoopBoolPref: sandbox.stub(),
|
getLoopBoolPref: sinon.stub(),
|
||||||
getCallData: sandbox.stub(),
|
getCallData: sinon.stub(),
|
||||||
releaseCallData: function() {},
|
releaseCallData: sinon.stub(),
|
||||||
startAlerting: function() {},
|
startAlerting: sinon.stub(),
|
||||||
stopAlerting: function() {},
|
stopAlerting: sinon.stub(),
|
||||||
ensureRegistered: function() {},
|
ensureRegistered: sinon.stub(),
|
||||||
get appVersionInfo() {
|
get appVersionInfo() {
|
||||||
return {
|
return {
|
||||||
version: "42",
|
version: "42",
|
||||||
|
@ -57,21 +71,19 @@ describe("loop.conversation", function() {
|
||||||
var oldTitle;
|
var oldTitle;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
oldTitle = document.title;
|
sandbox.stub(React, "renderComponent");
|
||||||
|
|
||||||
sandbox.stub(document.mozL10n, "initialize");
|
sandbox.stub(document.mozL10n, "initialize");
|
||||||
sandbox.stub(document.mozL10n, "get").returns("Fake title");
|
|
||||||
|
|
||||||
sandbox.stub(loop.conversation.ConversationRouter.prototype,
|
|
||||||
"initialize");
|
|
||||||
sandbox.stub(loop.shared.models.ConversationModel.prototype,
|
sandbox.stub(loop.shared.models.ConversationModel.prototype,
|
||||||
"initialize");
|
"initialize");
|
||||||
|
|
||||||
sandbox.stub(Backbone.history, "start");
|
window.OT = {
|
||||||
|
overrideGuidStorage: sinon.stub()
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
document.title = oldTitle;
|
delete window.OT;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initalize L10n", function() {
|
it("should initalize L10n", function() {
|
||||||
|
@ -82,300 +94,256 @@ describe("loop.conversation", function() {
|
||||||
navigator.mozLoop);
|
navigator.mozLoop);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set the document title", function() {
|
it("should create the IncomingConversationView", function() {
|
||||||
loop.conversation.init();
|
loop.conversation.init();
|
||||||
|
|
||||||
expect(document.title).to.be.equal("Fake title");
|
sinon.assert.calledOnce(React.renderComponent);
|
||||||
|
sinon.assert.calledWith(React.renderComponent,
|
||||||
|
sinon.match(function(value) {
|
||||||
|
return TestUtils.isDescriptorOfType(value,
|
||||||
|
loop.conversation.IncomingConversationView);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create the router", function() {
|
|
||||||
loop.conversation.init();
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(
|
|
||||||
loop.conversation.ConversationRouter.prototype.initialize);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should start Backbone history", function() {
|
|
||||||
loop.conversation.init();
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(Backbone.history.start);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ConversationRouter", function() {
|
describe("IncomingConversationView", function() {
|
||||||
var conversation, client;
|
var conversation, client, icView, oldTitle;
|
||||||
|
|
||||||
|
function mountTestComponent() {
|
||||||
|
return TestUtils.renderIntoDocument(
|
||||||
|
loop.conversation.IncomingConversationView({
|
||||||
|
client: client,
|
||||||
|
conversation: conversation,
|
||||||
|
notifications: notifications,
|
||||||
|
sdk: {}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
oldTitle = document.title;
|
||||||
client = new loop.Client();
|
client = new loop.Client();
|
||||||
conversation = new loop.shared.models.ConversationModel({}, {
|
conversation = new loop.shared.models.ConversationModel({}, {
|
||||||
sdk: {}
|
sdk: {}
|
||||||
});
|
});
|
||||||
sandbox.spy(conversation, "setIncomingSessionData");
|
conversation.set({callId: 42});
|
||||||
sandbox.stub(conversation, "setOutgoingSessionData");
|
sandbox.stub(conversation, "setOutgoingSessionData");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Routes", function() {
|
afterEach(function() {
|
||||||
var router;
|
icView = undefined;
|
||||||
|
document.title = oldTitle;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("start", function() {
|
||||||
|
it("should set the title to incoming_call_title2", function() {
|
||||||
|
sandbox.stub(document.mozL10n, "get", function(x) {
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
|
||||||
|
icView = mountTestComponent();
|
||||||
|
|
||||||
|
expect(document.title).eql("incoming_call_title2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("componentDidMount", function() {
|
||||||
|
var fakeSessionData;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
router = new ConversationRouter({
|
fakeSessionData = {
|
||||||
client: client,
|
sessionId: "sessionId",
|
||||||
conversation: conversation,
|
sessionToken: "sessionToken",
|
||||||
notifications: notifications
|
apiKey: "apiKey",
|
||||||
});
|
callType: "callType",
|
||||||
sandbox.stub(conversation, "incoming");
|
callId: "Hello",
|
||||||
|
progressURL: "http://progress.example.com",
|
||||||
|
websocketToken: "7b"
|
||||||
|
};
|
||||||
|
|
||||||
|
navigator.mozLoop.getCallData.returns(fakeSessionData);
|
||||||
|
stubComponent(loop.conversation, "IncomingCallView");
|
||||||
|
stubComponent(sharedView, "ConversationView");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#incoming", function() {
|
it("should start alerting", function() {
|
||||||
|
icView = mountTestComponent();
|
||||||
|
|
||||||
// XXX refactor to Just Work with "sandbox.stubComponent" or else
|
sinon.assert.calledOnce(navigator.mozLoop.startAlerting);
|
||||||
// just pass in the sandbox and put somewhere generally usable
|
});
|
||||||
|
|
||||||
function stubComponent(obj, component, mockTagName){
|
it("should call getCallData on navigator.mozLoop", function() {
|
||||||
var reactClass = React.createClass({
|
icView = mountTestComponent();
|
||||||
render: function() {
|
|
||||||
var mockTagName = mockTagName || "div";
|
|
||||||
return React.DOM[mockTagName](null, this.props.children);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return sandbox.stub(obj, component, reactClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function() {
|
sinon.assert.calledOnce(navigator.mozLoop.getCallData);
|
||||||
sandbox.stub(router, "loadReactComponent");
|
sinon.assert.calledWith(navigator.mozLoop.getCallData, 42);
|
||||||
stubComponent(loop.conversation, "IncomingCallView");
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it("should start alerting", function() {
|
describe("getCallData successful", function() {
|
||||||
sandbox.stub(navigator.mozLoop, "startAlerting");
|
var promise, resolveWebSocketConnect,
|
||||||
router.incoming("fakeVersion");
|
rejectWebSocketConnect;
|
||||||
|
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.startAlerting);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call getCallData on navigator.mozLoop",
|
|
||||||
function() {
|
|
||||||
router.incoming(42);
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.getCallData);
|
|
||||||
sinon.assert.calledWith(navigator.mozLoop.getCallData, 42);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getCallData successful", function() {
|
|
||||||
var fakeSessionData, resolvePromise, rejectPromise;
|
|
||||||
|
|
||||||
|
describe("Session Data setup", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
fakeSessionData = {
|
sandbox.stub(loop, "CallConnectionWebSocket").returns({
|
||||||
sessionId: "sessionId",
|
promiseConnect: function () {
|
||||||
sessionToken: "sessionToken",
|
promise = new Promise(function(resolve, reject) {
|
||||||
apiKey: "apiKey",
|
resolveWebSocketConnect = resolve;
|
||||||
callType: "callType",
|
rejectWebSocketConnect = reject;
|
||||||
callId: "Hello",
|
});
|
||||||
progressURL: "http://progress.example.com",
|
return promise;
|
||||||
websocketToken: 123
|
},
|
||||||
};
|
on: sinon.stub()
|
||||||
|
});
|
||||||
sandbox.stub(router, "_setupWebSocketAndCallView");
|
|
||||||
|
|
||||||
navigator.mozLoop.getCallData.returns(fakeSessionData);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should store the session data", function() {
|
it("should store the session data", function() {
|
||||||
router.incoming("fakeVersion");
|
sandbox.stub(conversation, "setIncomingSessionData");
|
||||||
|
|
||||||
|
icView = mountTestComponent();
|
||||||
|
|
||||||
sinon.assert.calledOnce(conversation.setIncomingSessionData);
|
sinon.assert.calledOnce(conversation.setIncomingSessionData);
|
||||||
sinon.assert.calledWithExactly(conversation.setIncomingSessionData,
|
sinon.assert.calledWithExactly(conversation.setIncomingSessionData,
|
||||||
fakeSessionData);
|
fakeSessionData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call #_setupWebSocketAndCallView", function() {
|
it("should setup the websocket connection", function() {
|
||||||
|
icView = mountTestComponent();
|
||||||
|
|
||||||
router.incoming("fakeVersion");
|
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
|
||||||
|
sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
|
||||||
sinon.assert.calledOnce(router._setupWebSocketAndCallView);
|
callId: "Hello",
|
||||||
sinon.assert.calledWithExactly(router._setupWebSocketAndCallView);
|
url: "http://progress.example.com",
|
||||||
|
websocketToken: "7b"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#_setupWebSocketAndCallView", function() {
|
describe("WebSocket Handling", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
conversation.setIncomingSessionData({
|
promise = new Promise(function(resolve, reject) {
|
||||||
sessionId: "sessionId",
|
resolveWebSocketConnect = resolve;
|
||||||
sessionToken: "sessionToken",
|
rejectWebSocketConnect = reject;
|
||||||
apiKey: "apiKey",
|
});
|
||||||
callType: "callType",
|
|
||||||
callId: "Hello",
|
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
|
||||||
progressURL: "http://progress.example.com",
|
});
|
||||||
websocketToken: 123
|
|
||||||
|
it("should set the state to incoming on success", function(done) {
|
||||||
|
icView = mountTestComponent();
|
||||||
|
resolveWebSocketConnect();
|
||||||
|
|
||||||
|
promise.then(function () {
|
||||||
|
expect(icView.state.callStatus).eql("incoming");
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Websocket connection successful", function() {
|
it("should display an error if the websocket failed to connect", function(done) {
|
||||||
var promise;
|
sandbox.stub(notifications, "errorL10n");
|
||||||
|
|
||||||
|
icView = mountTestComponent();
|
||||||
|
rejectWebSocketConnect();
|
||||||
|
|
||||||
|
promise.then(function() {
|
||||||
|
}, function () {
|
||||||
|
sinon.assert.calledOnce(notifications.errorL10n);
|
||||||
|
sinon.assert.calledWithExactly(notifications.errorL10n,
|
||||||
|
"cannot_start_call_session_not_ready");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("WebSocket Events", function() {
|
||||||
|
describe("Call cancelled or timed out before acceptance", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox.stub(loop, "CallConnectionWebSocket").returns({
|
icView = mountTestComponent();
|
||||||
promiseConnect: function() {
|
promise = new Promise(function(resolve, reject) {
|
||||||
promise = new Promise(function(resolve, reject) {
|
resolve();
|
||||||
resolve();
|
});
|
||||||
|
|
||||||
|
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
|
||||||
|
sandbox.stub(loop.CallConnectionWebSocket.prototype, "close");
|
||||||
|
sandbox.stub(window, "close");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("progress - terminated - cancel", function() {
|
||||||
|
it("should stop alerting", function(done) {
|
||||||
|
promise.then(function() {
|
||||||
|
icView._websocket.trigger("progress", {
|
||||||
|
state: "terminated",
|
||||||
|
reason: "cancel"
|
||||||
});
|
});
|
||||||
return promise;
|
|
||||||
},
|
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||||
on: sinon.spy()
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should close the websocket", function(done) {
|
||||||
|
promise.then(function() {
|
||||||
|
icView._websocket.trigger("progress", {
|
||||||
|
state: "terminated",
|
||||||
|
reason: "cancel"
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(icView._websocket.close);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should close the window", function(done) {
|
||||||
|
promise.then(function() {
|
||||||
|
icView._websocket.trigger("progress", {
|
||||||
|
state: "terminated",
|
||||||
|
reason: "cancel"
|
||||||
|
});
|
||||||
|
|
||||||
|
sandbox.clock.tick(1);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(window.close);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create a CallConnectionWebSocket", function(done) {
|
describe("progress - terminated - timeout (previousState = alerting)", function() {
|
||||||
router._setupWebSocketAndCallView();
|
it("should stop alerting", function(done) {
|
||||||
|
promise.then(function() {
|
||||||
|
icView._websocket.trigger("progress", {
|
||||||
|
state: "terminated",
|
||||||
|
reason: "timeout"
|
||||||
|
}, "alerting");
|
||||||
|
|
||||||
promise.then(function () {
|
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||||
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
|
done();
|
||||||
sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
|
|
||||||
callId: "Hello",
|
|
||||||
url: "http://progress.example.com",
|
|
||||||
// The websocket token is converted to a hex string.
|
|
||||||
websocketToken: "7b"
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create the view with video=false", function(done) {
|
|
||||||
sandbox.stub(conversation, "get").withArgs("callType").returns("audio");
|
|
||||||
|
|
||||||
router._setupWebSocketAndCallView();
|
|
||||||
|
|
||||||
promise.then(function () {
|
|
||||||
sinon.assert.called(conversation.get);
|
|
||||||
sinon.assert.calledOnce(loop.conversation.IncomingCallView);
|
|
||||||
sinon.assert.calledWithExactly(loop.conversation.IncomingCallView,
|
|
||||||
{model: conversation,
|
|
||||||
video: false});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Websocket connection failed", function() {
|
|
||||||
var promise;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
sandbox.stub(loop, "CallConnectionWebSocket").returns({
|
|
||||||
promiseConnect: function() {
|
|
||||||
promise = new Promise(function(resolve, reject) {
|
|
||||||
reject();
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
on: sinon.spy()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should display an error", function(done) {
|
|
||||||
sandbox.stub(notifications, "errorL10n");
|
|
||||||
router._setupWebSocketAndCallView();
|
|
||||||
|
|
||||||
promise.then(function() {
|
|
||||||
}, function () {
|
|
||||||
sinon.assert.calledOnce(router._notifications.errorL10n);
|
|
||||||
sinon.assert.calledWithExactly(router._notifications.errorL10n,
|
|
||||||
"cannot_start_call_session_not_ready");
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Events", function() {
|
|
||||||
describe("Call cancelled or timed out before acceptance", function() {
|
|
||||||
var promise;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect", function() {
|
|
||||||
promise = new Promise(function(resolve, reject) {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
});
|
|
||||||
sandbox.stub(loop.CallConnectionWebSocket.prototype, "close");
|
|
||||||
sandbox.stub(navigator.mozLoop, "stopAlerting");
|
|
||||||
sandbox.stub(window, "close");
|
|
||||||
|
|
||||||
router._setupWebSocketAndCallView();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("progress - terminated - cancel", function() {
|
|
||||||
it("should stop alerting", function(done) {
|
|
||||||
promise.then(function() {
|
|
||||||
router._websocket.trigger("progress", {
|
|
||||||
state: "terminated",
|
|
||||||
reason: "cancel"
|
|
||||||
});
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should close the websocket", function(done) {
|
|
||||||
promise.then(function() {
|
|
||||||
router._websocket.trigger("progress", {
|
|
||||||
state: "terminated",
|
|
||||||
reason: "cancel"
|
|
||||||
});
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router._websocket.close);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should close the window", function(done) {
|
|
||||||
promise.then(function() {
|
|
||||||
router._websocket.trigger("progress", {
|
|
||||||
state: "terminated",
|
|
||||||
reason: "cancel"
|
|
||||||
});
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(window.close);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("progress - terminated - timeout (previousState = alerting)", function() {
|
it("should close the websocket", function(done) {
|
||||||
it("should stop alerting", function(done) {
|
promise.then(function() {
|
||||||
promise.then(function() {
|
icView._websocket.trigger("progress", {
|
||||||
router._websocket.trigger("progress", {
|
state: "terminated",
|
||||||
state: "terminated",
|
reason: "timeout"
|
||||||
reason: "timeout"
|
}, "alerting");
|
||||||
}, "alerting");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
sinon.assert.calledOnce(icView._websocket.close);
|
||||||
done();
|
done();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should close the websocket", function(done) {
|
it("should close the window", function(done) {
|
||||||
promise.then(function() {
|
promise.then(function() {
|
||||||
router._websocket.trigger("progress", {
|
icView._websocket.trigger("progress", {
|
||||||
state: "terminated",
|
state: "terminated",
|
||||||
reason: "timeout"
|
reason: "timeout"
|
||||||
}, "alerting");
|
}, "alerting");
|
||||||
|
|
||||||
sinon.assert.calledOnce(router._websocket.close);
|
sandbox.clock.tick(1);
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should close the window", function(done) {
|
sinon.assert.calledOnce(window.close);
|
||||||
promise.then(function() {
|
done();
|
||||||
router._websocket.trigger("progress", {
|
|
||||||
state: "terminated",
|
|
||||||
reason: "timeout"
|
|
||||||
}, "alerting");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(window.close);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -385,6 +353,7 @@ describe("loop.conversation", function() {
|
||||||
|
|
||||||
describe("#accept", function() {
|
describe("#accept", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
icView = mountTestComponent();
|
||||||
conversation.setIncomingSessionData({
|
conversation.setIncomingSessionData({
|
||||||
sessionId: "sessionId",
|
sessionId: "sessionId",
|
||||||
sessionToken: "sessionToken",
|
sessionToken: "sessionToken",
|
||||||
|
@ -394,72 +363,38 @@ describe("loop.conversation", function() {
|
||||||
progressURL: "http://progress.example.com",
|
progressURL: "http://progress.example.com",
|
||||||
websocketToken: 123
|
websocketToken: 123
|
||||||
});
|
});
|
||||||
router._setupWebSocketAndCallView();
|
|
||||||
|
|
||||||
sandbox.stub(router._websocket, "accept");
|
sandbox.stub(icView._websocket, "accept");
|
||||||
sandbox.stub(navigator.mozLoop, "stopAlerting");
|
sandbox.stub(icView.props.conversation, "accepted");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initiate the conversation", function() {
|
it("should initiate the conversation", function() {
|
||||||
router.accept();
|
icView.accept();
|
||||||
|
|
||||||
sinon.assert.calledOnce(conversation.incoming);
|
sinon.assert.calledOnce(icView.props.conversation.accepted);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should notify the websocket of the user acceptance", function() {
|
it("should notify the websocket of the user acceptance", function() {
|
||||||
router.accept();
|
icView.accept();
|
||||||
|
|
||||||
sinon.assert.calledOnce(router._websocket.accept);
|
sinon.assert.calledOnce(icView._websocket.accept);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should stop alerting", function() {
|
it("should stop alerting", function() {
|
||||||
router.accept();
|
icView.accept();
|
||||||
|
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#conversation", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
sandbox.stub(router, "loadReactComponent");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should load the ConversationView if session is set", function() {
|
|
||||||
conversation.set("sessionId", "fakeSessionId");
|
|
||||||
|
|
||||||
router.conversation();
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.loadReactComponent);
|
|
||||||
sinon.assert.calledWith(router.loadReactComponent,
|
|
||||||
sinon.match(function(value) {
|
|
||||||
return TestUtils.isDescriptorOfType(value,
|
|
||||||
loop.shared.views.ConversationView);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not load the ConversationView if session is not set",
|
|
||||||
function() {
|
|
||||||
router.conversation();
|
|
||||||
|
|
||||||
sinon.assert.notCalled(router.loadReactComponent);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should notify the user when session is not set",
|
|
||||||
function() {
|
|
||||||
sandbox.stub(notifications, "errorL10n");
|
|
||||||
router.conversation();
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router._notifications.errorL10n);
|
|
||||||
sinon.assert.calledWithExactly(router._notifications.errorL10n,
|
|
||||||
"cannot_start_call_session_not_ready");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#decline", function() {
|
describe("#decline", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
icView = mountTestComponent();
|
||||||
|
|
||||||
sandbox.stub(window, "close");
|
sandbox.stub(window, "close");
|
||||||
router._websocket = {
|
icView._websocket = {
|
||||||
decline: sandbox.spy()
|
decline: sinon.stub(),
|
||||||
|
close: sinon.stub()
|
||||||
};
|
};
|
||||||
conversation.setIncomingSessionData({
|
conversation.setIncomingSessionData({
|
||||||
callId: 8699,
|
callId: 8699,
|
||||||
|
@ -468,76 +403,40 @@ describe("loop.conversation", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should close the window", function() {
|
it("should close the window", function() {
|
||||||
router.decline();
|
icView.decline();
|
||||||
|
|
||||||
sandbox.clock.tick(1);
|
sandbox.clock.tick(1);
|
||||||
|
|
||||||
sinon.assert.calledOnce(window.close);
|
sinon.assert.calledOnce(window.close);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should stop alerting", function() {
|
it("should stop alerting", function() {
|
||||||
sandbox.stub(navigator.mozLoop, "stopAlerting");
|
icView.decline();
|
||||||
router.decline();
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should release callData", function() {
|
it("should release callData", function() {
|
||||||
sandbox.stub(navigator.mozLoop, "releaseCallData");
|
icView.decline();
|
||||||
router.decline();
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
|
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
|
||||||
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, 8699);
|
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, 8699);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#feedback", function() {
|
|
||||||
var oldTitle;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
oldTitle = document.title;
|
|
||||||
sandbox.stub(document.mozL10n, "get").returns("Call ended");
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
sandbox.stub(loop, "FeedbackAPIClient");
|
|
||||||
sandbox.stub(router, "loadReactComponent");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
document.title = oldTitle;
|
|
||||||
});
|
|
||||||
|
|
||||||
// XXX When the call is ended gracefully, we should check that we
|
|
||||||
// close connections nicely (see bug 1046744)
|
|
||||||
it("should display a feedback form view", function() {
|
|
||||||
router.feedback();
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.loadReactComponent);
|
|
||||||
sinon.assert.calledWith(router.loadReactComponent,
|
|
||||||
sinon.match(function(value) {
|
|
||||||
return TestUtils.isDescriptorOfType(value,
|
|
||||||
loop.shared.views.FeedbackView);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should update the conversation window title", function() {
|
|
||||||
router.feedback();
|
|
||||||
|
|
||||||
expect(document.title).eql("Call ended");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#blocked", function() {
|
describe("#blocked", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
router._websocket = {
|
icView = mountTestComponent();
|
||||||
decline: sandbox.spy()
|
|
||||||
|
icView._websocket = {
|
||||||
|
decline: sinon.spy(),
|
||||||
|
close: sinon.stub()
|
||||||
};
|
};
|
||||||
sandbox.stub(window, "close");
|
sandbox.stub(window, "close");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call mozLoop.stopAlerting", function() {
|
it("should call mozLoop.stopAlerting", function() {
|
||||||
sandbox.stub(navigator.mozLoop, "stopAlerting");
|
icView.declineAndBlock();
|
||||||
router.declineAndBlock();
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||||
});
|
});
|
||||||
|
@ -547,7 +446,7 @@ describe("loop.conversation", function() {
|
||||||
.returns("fakeToken");
|
.returns("fakeToken");
|
||||||
var deleteCallUrl = sandbox.stub(loop.Client.prototype,
|
var deleteCallUrl = sandbox.stub(loop.Client.prototype,
|
||||||
"deleteCallUrl");
|
"deleteCallUrl");
|
||||||
router.declineAndBlock();
|
icView.declineAndBlock();
|
||||||
|
|
||||||
sinon.assert.calledOnce(deleteCallUrl);
|
sinon.assert.calledOnce(deleteCallUrl);
|
||||||
sinon.assert.calledWithExactly(deleteCallUrl, "fakeToken",
|
sinon.assert.calledWithExactly(deleteCallUrl, "fakeToken",
|
||||||
|
@ -556,7 +455,7 @@ describe("loop.conversation", function() {
|
||||||
|
|
||||||
it("should get callToken from conversation model", function() {
|
it("should get callToken from conversation model", function() {
|
||||||
sandbox.stub(conversation, "get");
|
sandbox.stub(conversation, "get");
|
||||||
router.declineAndBlock();
|
icView.declineAndBlock();
|
||||||
|
|
||||||
sinon.assert.calledTwice(conversation.get);
|
sinon.assert.calledTwice(conversation.get);
|
||||||
sinon.assert.calledWithExactly(conversation.get, "callToken");
|
sinon.assert.calledWithExactly(conversation.get, "callToken");
|
||||||
|
@ -572,14 +471,14 @@ describe("loop.conversation", function() {
|
||||||
sandbox.stub(loop.Client.prototype, "deleteCallUrl", function(_, cb) {
|
sandbox.stub(loop.Client.prototype, "deleteCallUrl", function(_, cb) {
|
||||||
cb(fakeError);
|
cb(fakeError);
|
||||||
});
|
});
|
||||||
router.declineAndBlock();
|
icView.declineAndBlock();
|
||||||
|
|
||||||
sinon.assert.calledOnce(log);
|
sinon.assert.calledOnce(log);
|
||||||
sinon.assert.calledWithExactly(log, fakeError);
|
sinon.assert.calledWithExactly(log, fakeError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should close the window", function() {
|
it("should close the window", function() {
|
||||||
router.declineAndBlock();
|
icView.declineAndBlock();
|
||||||
|
|
||||||
sandbox.clock.tick(1);
|
sandbox.clock.tick(1);
|
||||||
|
|
||||||
|
@ -589,63 +488,66 @@ describe("loop.conversation", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Events", function() {
|
describe("Events", function() {
|
||||||
var router, fakeSessionData;
|
var fakeSessionData;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
icView = mountTestComponent();
|
||||||
|
|
||||||
fakeSessionData = {
|
fakeSessionData = {
|
||||||
sessionId: "sessionId",
|
sessionId: "sessionId",
|
||||||
sessionToken: "sessionToken",
|
sessionToken: "sessionToken",
|
||||||
apiKey: "apiKey"
|
apiKey: "apiKey"
|
||||||
};
|
};
|
||||||
sandbox.stub(loop.conversation.ConversationRouter.prototype,
|
|
||||||
"navigate");
|
|
||||||
conversation.set("loopToken", "fakeToken");
|
conversation.set("loopToken", "fakeToken");
|
||||||
router = new loop.conversation.ConversationRouter({
|
navigator.mozLoop.getLoopCharPref.returns("http://fake");
|
||||||
client: client,
|
stubComponent(sharedView, "ConversationView");
|
||||||
conversation: conversation,
|
|
||||||
notifications: notifications
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate to call/ongoing once the call is ready",
|
describe("call:accepted", function() {
|
||||||
function() {
|
it("should display the ConversationView",
|
||||||
router.incoming(42);
|
function() {
|
||||||
|
conversation.accepted();
|
||||||
|
|
||||||
conversation.incoming();
|
TestUtils.findRenderedComponentWithType(icView,
|
||||||
|
sharedView.ConversationView);
|
||||||
sinon.assert.calledOnce(router.navigate);
|
});
|
||||||
sinon.assert.calledWith(router.navigate, "call/ongoing");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should navigate to call/feedback when the call session ends",
|
|
||||||
function() {
|
|
||||||
conversation.trigger("session:ended");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.navigate);
|
|
||||||
sinon.assert.calledWith(router.navigate, "call/feedback");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should navigate to call/feedback when peer hangs up", function() {
|
|
||||||
conversation.trigger("session:peer-hungup");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.navigate);
|
|
||||||
sinon.assert.calledWith(router.navigate, "call/feedback");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate to call/feedback when network disconnects",
|
describe("session:ended", function() {
|
||||||
function() {
|
it("should display the feedback view when the call session ends",
|
||||||
conversation.trigger("session:network-disconnected");
|
function() {
|
||||||
|
conversation.trigger("session:ended");
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.navigate);
|
TestUtils.findRenderedComponentWithType(icView,
|
||||||
sinon.assert.calledWith(router.navigate, "call/feedback");
|
sharedView.FeedbackView);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("session:peer-hungup", function() {
|
||||||
|
it("should display the feedback view when the peer hangs up",
|
||||||
|
function() {
|
||||||
|
conversation.trigger("session:peer-hungup");
|
||||||
|
|
||||||
|
TestUtils.findRenderedComponentWithType(icView,
|
||||||
|
sharedView.FeedbackView);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("session:peer-hungup", function() {
|
||||||
|
it("should navigate to call/feedback when network disconnects",
|
||||||
|
function() {
|
||||||
|
conversation.trigger("session:network-disconnected");
|
||||||
|
|
||||||
|
TestUtils.findRenderedComponentWithType(icView,
|
||||||
|
sharedView.FeedbackView);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Published and Subscribed Streams", function() {
|
describe("Published and Subscribed Streams", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
router._websocket = {
|
icView._websocket = {
|
||||||
mediaUp: sinon.spy()
|
mediaUp: sinon.spy()
|
||||||
};
|
};
|
||||||
router.incoming("fakeVersion");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("publishStream", function() {
|
describe("publishStream", function() {
|
||||||
|
@ -653,7 +555,7 @@ describe("loop.conversation", function() {
|
||||||
function() {
|
function() {
|
||||||
conversation.set("publishedStream", true);
|
conversation.set("publishedStream", true);
|
||||||
|
|
||||||
sinon.assert.notCalled(router._websocket.mediaUp);
|
sinon.assert.notCalled(icView._websocket.mediaUp);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should notify the websocket that media is up if both streams" +
|
it("should notify the websocket that media is up if both streams" +
|
||||||
|
@ -661,7 +563,7 @@ describe("loop.conversation", function() {
|
||||||
conversation.set("subscribedStream", true);
|
conversation.set("subscribedStream", true);
|
||||||
conversation.set("publishedStream", true);
|
conversation.set("publishedStream", true);
|
||||||
|
|
||||||
sinon.assert.calledOnce(router._websocket.mediaUp);
|
sinon.assert.calledOnce(icView._websocket.mediaUp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -670,7 +572,7 @@ describe("loop.conversation", function() {
|
||||||
function() {
|
function() {
|
||||||
conversation.set("subscribedStream", true);
|
conversation.set("subscribedStream", true);
|
||||||
|
|
||||||
sinon.assert.notCalled(router._websocket.mediaUp);
|
sinon.assert.notCalled(icView._websocket.mediaUp);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should notify the websocket that media is up if both streams" +
|
it("should notify the websocket that media is up if both streams" +
|
||||||
|
@ -678,7 +580,7 @@ describe("loop.conversation", function() {
|
||||||
conversation.set("publishedStream", true);
|
conversation.set("publishedStream", true);
|
||||||
conversation.set("subscribedStream", true);
|
conversation.set("subscribedStream", true);
|
||||||
|
|
||||||
sinon.assert.calledOnce(router._websocket.mediaUp);
|
sinon.assert.calledOnce(icView._websocket.mediaUp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,12 +35,10 @@
|
||||||
<script src="../../content/shared/js/utils.js"></script>
|
<script src="../../content/shared/js/utils.js"></script>
|
||||||
<script src="../../content/shared/js/feedbackApiClient.js"></script>
|
<script src="../../content/shared/js/feedbackApiClient.js"></script>
|
||||||
<script src="../../content/shared/js/models.js"></script>
|
<script src="../../content/shared/js/models.js"></script>
|
||||||
<script src="../../content/shared/js/router.js"></script>
|
|
||||||
<script src="../../content/shared/js/mixins.js"></script>
|
<script src="../../content/shared/js/mixins.js"></script>
|
||||||
<script src="../../content/shared/js/views.js"></script>
|
<script src="../../content/shared/js/views.js"></script>
|
||||||
<script src="../../content/shared/js/websocket.js"></script>
|
<script src="../../content/shared/js/websocket.js"></script>
|
||||||
<script src="../../content/js/client.js"></script>
|
<script src="../../content/js/client.js"></script>
|
||||||
<script src="../../content/js/desktopRouter.js"></script>
|
|
||||||
<script src="../../content/js/conversation.js"></script>
|
<script src="../../content/js/conversation.js"></script>
|
||||||
<script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
|
<script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
|
||||||
<script src="../../content/js/panel.js"></script>
|
<script src="../../content/js/panel.js"></script>
|
||||||
|
|
|
@ -13,13 +13,6 @@ describe("loop.panel", function() {
|
||||||
|
|
||||||
var sandbox, notifications, fakeXHR, requests = [];
|
var sandbox, notifications, fakeXHR, requests = [];
|
||||||
|
|
||||||
function createTestRouter(fakeDocument) {
|
|
||||||
return new loop.panel.PanelRouter({
|
|
||||||
notifications: notifications,
|
|
||||||
document: fakeDocument
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = sinon.sandbox.create();
|
sandbox = sinon.sandbox.create();
|
||||||
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
<script src="../../content/shared/js/models.js"></script>
|
<script src="../../content/shared/js/models.js"></script>
|
||||||
<script src="../../content/shared/js/mixins.js"></script>
|
<script src="../../content/shared/js/mixins.js"></script>
|
||||||
<script src="../../content/shared/js/views.js"></script>
|
<script src="../../content/shared/js/views.js"></script>
|
||||||
<script src="../../content/shared/js/router.js"></script>
|
|
||||||
<script src="../../content/shared/js/websocket.js"></script>
|
<script src="../../content/shared/js/websocket.js"></script>
|
||||||
<script src="../../content/shared/js/feedbackApiClient.js"></script>
|
<script src="../../content/shared/js/feedbackApiClient.js"></script>
|
||||||
|
|
||||||
|
@ -46,7 +45,6 @@
|
||||||
<script src="mixins_test.js"></script>
|
<script src="mixins_test.js"></script>
|
||||||
<script src="utils_test.js"></script>
|
<script src="utils_test.js"></script>
|
||||||
<script src="views_test.js"></script>
|
<script src="views_test.js"></script>
|
||||||
<script src="router_test.js"></script>
|
|
||||||
<script src="websocket_test.js"></script>
|
<script src="websocket_test.js"></script>
|
||||||
<script src="feedbackApiClient_test.js"></script>
|
<script src="feedbackApiClient_test.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -65,13 +65,13 @@ describe("loop.shared.models", function() {
|
||||||
conversation.set("loopToken", "fakeToken");
|
conversation.set("loopToken", "fakeToken");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#incoming", function() {
|
describe("#accepted", function() {
|
||||||
it("should trigger a `call:incoming` event", function(done) {
|
it("should trigger a `call:accepted` event", function(done) {
|
||||||
conversation.once("call:incoming", function() {
|
conversation.once("call:accepted", function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
conversation.incoming();
|
conversation.accepted();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,150 +0,0 @@
|
||||||
/* 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, sinon */
|
|
||||||
|
|
||||||
var expect = chai.expect;
|
|
||||||
|
|
||||||
describe("loop.shared.router", function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var sandbox, notifications;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
sandbox = sinon.sandbox.create();
|
|
||||||
notifications = new loop.shared.models.NotificationCollection();
|
|
||||||
sandbox.stub(notifications, "errorL10n");
|
|
||||||
sandbox.stub(notifications, "warnL10n");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("BaseRouter", function() {
|
|
||||||
beforeEach(function() {
|
|
||||||
$("#fixtures").html('<div id="main"></div>');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
$("#fixtures").empty();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#constructor", function() {
|
|
||||||
it("should require a notifications collection", function() {
|
|
||||||
expect(function() {
|
|
||||||
new loop.shared.router.BaseRouter();
|
|
||||||
}).to.Throw(Error, /missing required notifications/);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("inherited", function() {
|
|
||||||
var ExtendedRouter = loop.shared.router.BaseRouter.extend({});
|
|
||||||
|
|
||||||
it("should require a notifications collection", function() {
|
|
||||||
expect(function() {
|
|
||||||
new ExtendedRouter();
|
|
||||||
}).to.Throw(Error, /missing required notifications/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("BaseConversationRouter", function() {
|
|
||||||
var conversation, TestRouter;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
TestRouter = loop.shared.router.BaseConversationRouter.extend({
|
|
||||||
endCall: sandbox.spy()
|
|
||||||
});
|
|
||||||
conversation = new loop.shared.models.ConversationModel({
|
|
||||||
loopToken: "fakeToken"
|
|
||||||
}, {
|
|
||||||
sdk: {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#constructor", function() {
|
|
||||||
it("should require a ConversationModel instance", function() {
|
|
||||||
expect(function() {
|
|
||||||
new TestRouter({ client: {} });
|
|
||||||
}).to.Throw(Error, /missing required conversation/);
|
|
||||||
});
|
|
||||||
it("should require a Client instance", function() {
|
|
||||||
expect(function() {
|
|
||||||
new TestRouter({ conversation: {} });
|
|
||||||
}).to.Throw(Error, /missing required client/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Events", function() {
|
|
||||||
var router, fakeSessionData;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
fakeSessionData = {
|
|
||||||
sessionId: "sessionId",
|
|
||||||
sessionToken: "sessionToken",
|
|
||||||
apiKey: "apiKey"
|
|
||||||
};
|
|
||||||
router = new TestRouter({
|
|
||||||
conversation: conversation,
|
|
||||||
notifications: notifications,
|
|
||||||
client: {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("session:connection-error", function() {
|
|
||||||
|
|
||||||
it("should warn the user when .connect() call fails", function() {
|
|
||||||
conversation.trigger("session:connection-error");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(notifications.errorL10n);
|
|
||||||
sinon.assert.calledWithExactly(notifications.errorL10n, sinon.match.string);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should invoke endCall()", function() {
|
|
||||||
conversation.trigger("session:connection-error");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.endCall);
|
|
||||||
sinon.assert.calledWithExactly(router.endCall);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call endCall() when conversation ended", function() {
|
|
||||||
conversation.trigger("session:ended");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.endCall);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should warn the user when peer hangs up", function() {
|
|
||||||
conversation.trigger("session:peer-hungup");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(notifications.warnL10n);
|
|
||||||
sinon.assert.calledWithExactly(notifications.warnL10n,
|
|
||||||
"peer_ended_conversation2");
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call endCall() when peer hangs up", function() {
|
|
||||||
conversation.trigger("session:peer-hungup");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.endCall);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should warn the user when network disconnects", function() {
|
|
||||||
conversation.trigger("session:network-disconnected");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(notifications.warnL10n);
|
|
||||||
sinon.assert.calledWithExactly(notifications.warnL10n,
|
|
||||||
"network_disconnected");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call endCall() when network disconnects", function() {
|
|
||||||
conversation.trigger("session:network-disconnected");
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(router.endCall);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -21,6 +21,40 @@ describe("loop.shared.utils", function() {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Helper", function() {
|
||||||
|
var helper;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
helper = new sharedUtils.Helper();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#isIOS", function() {
|
||||||
|
it("should detect iOS", function() {
|
||||||
|
expect(helper.isIOS("iPad")).eql(true);
|
||||||
|
expect(helper.isIOS("iPod")).eql(true);
|
||||||
|
expect(helper.isIOS("iPhone")).eql(true);
|
||||||
|
expect(helper.isIOS("iPhone Simulator")).eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't detect iOS with other platforms", function() {
|
||||||
|
expect(helper.isIOS("MacIntel")).eql(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#isFirefox", function() {
|
||||||
|
it("should detect Firefox", function() {
|
||||||
|
expect(helper.isFirefox("Firefox")).eql(true);
|
||||||
|
expect(helper.isFirefox("Gecko/Firefox")).eql(true);
|
||||||
|
expect(helper.isFirefox("Firefox/Gecko")).eql(true);
|
||||||
|
expect(helper.isFirefox("Gecko/Firefox/Chuck Norris")).eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't detect Firefox with other platforms", function() {
|
||||||
|
expect(helper.isFirefox("Opera")).eql(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("#getBoolPreference", function() {
|
describe("#getBoolPreference", function() {
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
navigator.mozLoop = undefined;
|
navigator.mozLoop = undefined;
|
||||||
|
|
|
@ -12,6 +12,7 @@ describe("loop.webapp", function() {
|
||||||
|
|
||||||
var sharedModels = loop.shared.models,
|
var sharedModels = loop.shared.models,
|
||||||
sharedViews = loop.shared.views,
|
sharedViews = loop.shared.views,
|
||||||
|
sharedUtils = loop.shared.utils,
|
||||||
sandbox,
|
sandbox,
|
||||||
notifications,
|
notifications,
|
||||||
feedbackApiClient;
|
feedbackApiClient;
|
||||||
|
@ -33,7 +34,7 @@ describe("loop.webapp", function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox.stub(React, "renderComponent");
|
sandbox.stub(React, "renderComponent");
|
||||||
sandbox.stub(loop.webapp.WebappHelper.prototype,
|
sandbox.stub(sharedUtils.Helper.prototype,
|
||||||
"locationHash").returns("#call/fake-Token");
|
"locationHash").returns("#call/fake-Token");
|
||||||
loop.config.feedbackApiUrl = "http://fake.invalid";
|
loop.config.feedbackApiUrl = "http://fake.invalid";
|
||||||
conversationSetStub =
|
conversationSetStub =
|
||||||
|
@ -78,7 +79,7 @@ describe("loop.webapp", function() {
|
||||||
});
|
});
|
||||||
conversation.set("loopToken", "fakeToken");
|
conversation.set("loopToken", "fakeToken");
|
||||||
ocView = mountTestComponent({
|
ocView = mountTestComponent({
|
||||||
helper: new loop.webapp.WebappHelper(),
|
helper: new sharedUtils.Helper(),
|
||||||
client: client,
|
client: client,
|
||||||
conversation: conversation,
|
conversation: conversation,
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
|
@ -473,13 +474,13 @@ describe("loop.webapp", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("WebappRootView", function() {
|
describe("WebappRootView", function() {
|
||||||
var webappHelper, sdk, conversationModel, client, props;
|
var helper, sdk, conversationModel, client, props;
|
||||||
|
|
||||||
function mountTestComponent() {
|
function mountTestComponent() {
|
||||||
return TestUtils.renderIntoDocument(
|
return TestUtils.renderIntoDocument(
|
||||||
loop.webapp.WebappRootView({
|
loop.webapp.WebappRootView({
|
||||||
client: client,
|
client: client,
|
||||||
helper: webappHelper,
|
helper: helper,
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
sdk: sdk,
|
sdk: sdk,
|
||||||
conversation: conversationModel,
|
conversation: conversationModel,
|
||||||
|
@ -488,7 +489,7 @@ describe("loop.webapp", function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
webappHelper = new loop.webapp.WebappHelper();
|
helper = new sharedUtils.Helper();
|
||||||
sdk = {
|
sdk = {
|
||||||
checkSystemRequirements: function() { return true; }
|
checkSystemRequirements: function() { return true; }
|
||||||
};
|
};
|
||||||
|
@ -505,7 +506,7 @@ describe("loop.webapp", function() {
|
||||||
|
|
||||||
it("should mount the unsupportedDevice view if the device is running iOS",
|
it("should mount the unsupportedDevice view if the device is running iOS",
|
||||||
function() {
|
function() {
|
||||||
sandbox.stub(webappHelper, "isIOS").returns(true);
|
sandbox.stub(helper, "isIOS").returns(true);
|
||||||
|
|
||||||
var webappRootView = mountTestComponent();
|
var webappRootView = mountTestComponent();
|
||||||
|
|
||||||
|
@ -825,38 +826,4 @@ describe("loop.webapp", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("WebappHelper", function() {
|
|
||||||
var helper;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
helper = new loop.webapp.WebappHelper();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#isIOS", function() {
|
|
||||||
it("should detect iOS", function() {
|
|
||||||
expect(helper.isIOS("iPad")).eql(true);
|
|
||||||
expect(helper.isIOS("iPod")).eql(true);
|
|
||||||
expect(helper.isIOS("iPhone")).eql(true);
|
|
||||||
expect(helper.isIOS("iPhone Simulator")).eql(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shouldn't detect iOS with other platforms", function() {
|
|
||||||
expect(helper.isIOS("MacIntel")).eql(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#isFirefox", function() {
|
|
||||||
it("should detect Firefox", function() {
|
|
||||||
expect(helper.isFirefox("Firefox")).eql(true);
|
|
||||||
expect(helper.isFirefox("Gecko/Firefox")).eql(true);
|
|
||||||
expect(helper.isFirefox("Firefox/Gecko")).eql(true);
|
|
||||||
expect(helper.isFirefox("Gecko/Firefox/Chuck Norris")).eql(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shouldn't detect Firefox with other platforms", function() {
|
|
||||||
expect(helper.isFirefox("Opera")).eql(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,11 +34,9 @@
|
||||||
<script src="../content/shared/js/feedbackApiClient.js"></script>
|
<script src="../content/shared/js/feedbackApiClient.js"></script>
|
||||||
<script src="../content/shared/js/utils.js"></script>
|
<script src="../content/shared/js/utils.js"></script>
|
||||||
<script src="../content/shared/js/models.js"></script>
|
<script src="../content/shared/js/models.js"></script>
|
||||||
<script src="../content/shared/js/router.js"></script>
|
|
||||||
<script src="../content/shared/js/mixins.js"></script>
|
<script src="../content/shared/js/mixins.js"></script>
|
||||||
<script src="../content/shared/js/views.js"></script>
|
<script src="../content/shared/js/views.js"></script>
|
||||||
<script src="../content/js/client.js"></script>
|
<script src="../content/js/client.js"></script>
|
||||||
<script src="../content/js/desktopRouter.js"></script>
|
|
||||||
<script src="../standalone/content/js/webapp.js"></script>
|
<script src="../standalone/content/js/webapp.js"></script>
|
||||||
<script type="text/javascript;version=1.8" src="../content/js/contacts.js"></script>
|
<script type="text/javascript;version=1.8" src="../content/js/contacts.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче