зеркало из 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/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/views.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/js/client.js"></script>
|
||||
<script type="text/javascript" src="loop/js/desktopRouter.js"></script>
|
||||
<script type="text/javascript" src="loop/js/conversation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -8,16 +8,11 @@
|
|||
/* global loop:true, React */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.conversation = (function(OT, mozL10n) {
|
||||
loop.conversation = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
/**
|
||||
* App router.
|
||||
* @type {loop.desktopRouter.DesktopConversationRouter}
|
||||
*/
|
||||
var router;
|
||||
var sharedViews = loop.shared.views,
|
||||
sharedModels = loop.shared.models;
|
||||
|
||||
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:
|
||||
* - {loop.shared.models.ConversationModel} conversation Conversation model.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*
|
||||
* @type {loop.shared.router.BaseConversationRouter}
|
||||
* At the moment, it does more than that, these parts need refactoring out.
|
||||
*/
|
||||
var ConversationRouter = loop.desktopRouter.DesktopConversationRouter.extend({
|
||||
routes: {
|
||||
"incoming/:callId": "incoming",
|
||||
"call/accept": "accept",
|
||||
"call/decline": "decline",
|
||||
"call/ongoing": "conversation",
|
||||
"call/declineAndBlock": "declineAndBlock",
|
||||
"call/shutdown": "shutdown",
|
||||
"call/feedback": "feedback"
|
||||
var IncomingConversationView = React.createClass({displayName: 'IncomingConversationView',
|
||||
propTypes: {
|
||||
client: React.PropTypes.instanceOf(loop.Client).isRequired,
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
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() {
|
||||
this.navigate("call/ongoing", {trigger: true});
|
||||
_notifyError: function(error) {
|
||||
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() {
|
||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
||||
this.navigate("call/feedback", {trigger: true});
|
||||
_onPeerHungup: function() {
|
||||
this.props.notifications.warnL10n("peer_ended_conversation2");
|
||||
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.
|
||||
*
|
||||
* @param {String} callId Identifier assigned by the LoopService
|
||||
* to this incoming call.
|
||||
*/
|
||||
incoming: function(callId) {
|
||||
setupIncomingCall: function() {
|
||||
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) {
|
||||
console.error("Failed to get the call data");
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// 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;
|
||||
}
|
||||
this._conversation.setIncomingSessionData(callData);
|
||||
this._setupWebSocketAndCallView();
|
||||
this.props.conversation.setIncomingSessionData(callData);
|
||||
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
|
||||
* call view if appropriate.
|
||||
*/
|
||||
_setupWebSocketAndCallView: function() {
|
||||
_setupWebSocket: function() {
|
||||
this._websocket = new loop.CallConnectionWebSocket({
|
||||
url: this._conversation.get("progressURL"),
|
||||
websocketToken: this._conversation.get("websocketToken"),
|
||||
callId: this._conversation.get("callId"),
|
||||
url: this.props.conversation.get("progressURL"),
|
||||
websocketToken: this.props.conversation.get("websocketToken"),
|
||||
callId: this.props.conversation.get("callId"),
|
||||
});
|
||||
this._websocket.promiseConnect().then(function() {
|
||||
this.loadReactComponent(loop.conversation.IncomingCallView({
|
||||
model: this._conversation,
|
||||
video: this._conversation.hasVideoStream("incoming")
|
||||
}));
|
||||
this.setState({callStatus: "incoming"});
|
||||
}.bind(this), function() {
|
||||
this._handleSessionError();
|
||||
return;
|
||||
|
@ -301,7 +387,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
_checkConnected: function() {
|
||||
// Check we've had both local and remote streams connected before
|
||||
// sending the media up message.
|
||||
if (this._conversation.streamsConnected()) {
|
||||
if (this.props.conversation.streamsConnected()) {
|
||||
this._websocket.mediaUp();
|
||||
}
|
||||
},
|
||||
|
@ -337,6 +423,12 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
_abortIncomingCall: function() {
|
||||
navigator.mozLoop.stopAlerting();
|
||||
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();
|
||||
},
|
||||
|
||||
|
@ -346,7 +438,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
accept: function() {
|
||||
navigator.mozLoop.stopAlerting();
|
||||
this._websocket.accept();
|
||||
this._conversation.incoming();
|
||||
this.props.conversation.accepted();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -354,13 +446,11 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
*/
|
||||
_declineCall: function() {
|
||||
this._websocket.decline();
|
||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
||||
// XXX Don't close the window straight away, but let any sends happen
|
||||
// first. Ideally we'd wait to close the window until after we have a
|
||||
// response from the server, to know that everything has completed
|
||||
// successfully. However, that's quite difficult to ensure at the
|
||||
// moment so we'll add it later.
|
||||
setTimeout(window.close, 0);
|
||||
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -379,8 +469,8 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
*/
|
||||
declineAndBlock: function() {
|
||||
navigator.mozLoop.stopAlerting();
|
||||
var token = this._conversation.get("callToken");
|
||||
this._client.deleteCallUrl(token, function(error) {
|
||||
var token = this.props.conversation.get("callToken");
|
||||
this.props.client.deleteCallUrl(token, function(error) {
|
||||
// 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
|
||||
// (bug 1048909).
|
||||
|
@ -389,62 +479,14 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
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
|
||||
*/
|
||||
_handleSessionError: function() {
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// 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
|
||||
// don't work in the conversation window
|
||||
if (OT && OT.hasOwnProperty("overrideGuidStorage")) {
|
||||
OT.overrideGuidStorage({
|
||||
get: function(callback) {
|
||||
callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
|
||||
},
|
||||
set: function(guid, callback) {
|
||||
navigator.mozLoop.setLoopCharPref("ot.guid", guid);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.title = mozL10n.get("incoming_call_title2");
|
||||
window.OT.overrideGuidStorage({
|
||||
get: function(callback) {
|
||||
callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
|
||||
},
|
||||
set: function(guid, callback) {
|
||||
navigator.mozLoop.setLoopCharPref("ot.guid", guid);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
||||
|
||||
var client = new loop.Client();
|
||||
router = new ConversationRouter({
|
||||
client: client,
|
||||
conversation: new loop.shared.models.ConversationModel(
|
||||
{}, // Model attributes
|
||||
{sdk: OT}), // Model dependencies
|
||||
notifications: new loop.shared.models.NotificationCollection()
|
||||
});
|
||||
var conversation = new sharedModels.ConversationModel(
|
||||
{}, // Model attributes
|
||||
{sdk: window.OT} // Model dependencies
|
||||
);
|
||||
var notifications = new sharedModels.NotificationCollection();
|
||||
|
||||
window.addEventListener("unload", function(event) {
|
||||
// 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 {
|
||||
ConversationRouter: ConversationRouter,
|
||||
IncomingConversationView: IncomingConversationView,
|
||||
IncomingCallView: IncomingCallView,
|
||||
init: init
|
||||
};
|
||||
})(window.OT, document.mozL10n);
|
||||
})(document.mozL10n);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loop.conversation.init);
|
||||
|
|
|
@ -8,16 +8,11 @@
|
|||
/* global loop:true, React */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.conversation = (function(OT, mozL10n) {
|
||||
loop.conversation = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
/**
|
||||
* App router.
|
||||
* @type {loop.desktopRouter.DesktopConversationRouter}
|
||||
*/
|
||||
var router;
|
||||
var sharedViews = loop.shared.views,
|
||||
sharedModels = loop.shared.models;
|
||||
|
||||
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:
|
||||
* - {loop.shared.models.ConversationModel} conversation Conversation model.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*
|
||||
* @type {loop.shared.router.BaseConversationRouter}
|
||||
* At the moment, it does more than that, these parts need refactoring out.
|
||||
*/
|
||||
var ConversationRouter = loop.desktopRouter.DesktopConversationRouter.extend({
|
||||
routes: {
|
||||
"incoming/:callId": "incoming",
|
||||
"call/accept": "accept",
|
||||
"call/decline": "decline",
|
||||
"call/ongoing": "conversation",
|
||||
"call/declineAndBlock": "declineAndBlock",
|
||||
"call/shutdown": "shutdown",
|
||||
"call/feedback": "feedback"
|
||||
var IncomingConversationView = React.createClass({
|
||||
propTypes: {
|
||||
client: React.PropTypes.instanceOf(loop.Client).isRequired,
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
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() {
|
||||
this.navigate("call/ongoing", {trigger: true});
|
||||
_notifyError: function(error) {
|
||||
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() {
|
||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
||||
this.navigate("call/feedback", {trigger: true});
|
||||
_onPeerHungup: function() {
|
||||
this.props.notifications.warnL10n("peer_ended_conversation2");
|
||||
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.
|
||||
*
|
||||
* @param {String} callId Identifier assigned by the LoopService
|
||||
* to this incoming call.
|
||||
*/
|
||||
incoming: function(callId) {
|
||||
setupIncomingCall: function() {
|
||||
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) {
|
||||
console.error("Failed to get the call data");
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// 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;
|
||||
}
|
||||
this._conversation.setIncomingSessionData(callData);
|
||||
this._setupWebSocketAndCallView();
|
||||
this.props.conversation.setIncomingSessionData(callData);
|
||||
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
|
||||
* call view if appropriate.
|
||||
*/
|
||||
_setupWebSocketAndCallView: function() {
|
||||
_setupWebSocket: function() {
|
||||
this._websocket = new loop.CallConnectionWebSocket({
|
||||
url: this._conversation.get("progressURL"),
|
||||
websocketToken: this._conversation.get("websocketToken"),
|
||||
callId: this._conversation.get("callId"),
|
||||
url: this.props.conversation.get("progressURL"),
|
||||
websocketToken: this.props.conversation.get("websocketToken"),
|
||||
callId: this.props.conversation.get("callId"),
|
||||
});
|
||||
this._websocket.promiseConnect().then(function() {
|
||||
this.loadReactComponent(loop.conversation.IncomingCallView({
|
||||
model: this._conversation,
|
||||
video: this._conversation.hasVideoStream("incoming")
|
||||
}));
|
||||
this.setState({callStatus: "incoming"});
|
||||
}.bind(this), function() {
|
||||
this._handleSessionError();
|
||||
return;
|
||||
|
@ -301,7 +387,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
_checkConnected: function() {
|
||||
// Check we've had both local and remote streams connected before
|
||||
// sending the media up message.
|
||||
if (this._conversation.streamsConnected()) {
|
||||
if (this.props.conversation.streamsConnected()) {
|
||||
this._websocket.mediaUp();
|
||||
}
|
||||
},
|
||||
|
@ -337,6 +423,12 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
_abortIncomingCall: function() {
|
||||
navigator.mozLoop.stopAlerting();
|
||||
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();
|
||||
},
|
||||
|
||||
|
@ -346,7 +438,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
accept: function() {
|
||||
navigator.mozLoop.stopAlerting();
|
||||
this._websocket.accept();
|
||||
this._conversation.incoming();
|
||||
this.props.conversation.accepted();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -354,13 +446,11 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
*/
|
||||
_declineCall: function() {
|
||||
this._websocket.decline();
|
||||
navigator.mozLoop.releaseCallData(this._conversation.get("callId"));
|
||||
// XXX Don't close the window straight away, but let any sends happen
|
||||
// first. Ideally we'd wait to close the window until after we have a
|
||||
// response from the server, to know that everything has completed
|
||||
// successfully. However, that's quite difficult to ensure at the
|
||||
// moment so we'll add it later.
|
||||
setTimeout(window.close, 0);
|
||||
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -379,8 +469,8 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
*/
|
||||
declineAndBlock: function() {
|
||||
navigator.mozLoop.stopAlerting();
|
||||
var token = this._conversation.get("callToken");
|
||||
this._client.deleteCallUrl(token, function(error) {
|
||||
var token = this.props.conversation.get("callToken");
|
||||
this.props.client.deleteCallUrl(token, function(error) {
|
||||
// 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
|
||||
// (bug 1048909).
|
||||
|
@ -389,62 +479,14 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
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
|
||||
*/
|
||||
_handleSessionError: function() {
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// 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
|
||||
// don't work in the conversation window
|
||||
if (OT && OT.hasOwnProperty("overrideGuidStorage")) {
|
||||
OT.overrideGuidStorage({
|
||||
get: function(callback) {
|
||||
callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
|
||||
},
|
||||
set: function(guid, callback) {
|
||||
navigator.mozLoop.setLoopCharPref("ot.guid", guid);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.title = mozL10n.get("incoming_call_title2");
|
||||
window.OT.overrideGuidStorage({
|
||||
get: function(callback) {
|
||||
callback(null, navigator.mozLoop.getLoopCharPref("ot.guid"));
|
||||
},
|
||||
set: function(guid, callback) {
|
||||
navigator.mozLoop.setLoopCharPref("ot.guid", guid);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
||||
|
||||
var client = new loop.Client();
|
||||
router = new ConversationRouter({
|
||||
client: client,
|
||||
conversation: new loop.shared.models.ConversationModel(
|
||||
{}, // Model attributes
|
||||
{sdk: OT}), // Model dependencies
|
||||
notifications: new loop.shared.models.NotificationCollection()
|
||||
});
|
||||
var conversation = new sharedModels.ConversationModel(
|
||||
{}, // Model attributes
|
||||
{sdk: window.OT} // Model dependencies
|
||||
);
|
||||
var notifications = new sharedModels.NotificationCollection();
|
||||
|
||||
window.addEventListener("unload", function(event) {
|
||||
// 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 {
|
||||
ConversationRouter: ConversationRouter,
|
||||
IncomingConversationView: IncomingConversationView,
|
||||
IncomingCallView: IncomingCallView,
|
||||
init: init
|
||||
};
|
||||
})(window.OT, document.mozL10n);
|
||||
})(document.mozL10n);
|
||||
|
||||
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 __ = mozL10n.get; // aliasing translation function as __ for concision
|
||||
|
||||
/**
|
||||
* Panel router.
|
||||
* @type {loop.desktopRouter.DesktopRouter}
|
||||
*/
|
||||
var router;
|
||||
|
||||
var TabView = React.createClass({displayName: 'TabView',
|
||||
getInitialState: function() {
|
||||
return {
|
||||
|
|
|
@ -17,12 +17,6 @@ loop.panel = (function(_, mozL10n) {
|
|||
var ContactsList = loop.contacts.ContactsList;
|
||||
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
||||
|
||||
/**
|
||||
* Panel router.
|
||||
* @type {loop.desktopRouter.DesktopRouter}
|
||||
*/
|
||||
var router;
|
||||
|
||||
var TabView = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
|
|
|
@ -77,10 +77,10 @@ loop.shared.models = (function(l10n) {
|
|||
},
|
||||
|
||||
/**
|
||||
* Starts an incoming conversation.
|
||||
* Indicates an incoming conversation has been accepted.
|
||||
*/
|
||||
incoming: function() {
|
||||
this.trigger("call:incoming");
|
||||
accepted: function() {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
Helper: Helper,
|
||||
getTargetPlatform: getTargetPlatform,
|
||||
getBoolPreference: getBoolPreference
|
||||
};
|
||||
|
|
|
@ -12,7 +12,6 @@ browser.jar:
|
|||
|
||||
# Desktop script
|
||||
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/otconfig.js (content/js/otconfig.js)
|
||||
content/browser/loop/js/panel.js (content/js/panel.js)
|
||||
|
@ -55,7 +54,6 @@ browser.jar:
|
|||
# Shared scripts
|
||||
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/router.js (content/shared/js/router.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/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";
|
||||
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedViews = loop.shared.views;
|
||||
sharedViews = loop.shared.views,
|
||||
sharedUtils = loop.shared.utils;
|
||||
|
||||
/**
|
||||
* Homepage view.
|
||||
|
@ -435,7 +436,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
|
||||
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
|
||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
|
@ -690,7 +691,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
|
||||
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
|
||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||
.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.
|
||||
*/
|
||||
function init() {
|
||||
var helper = new WebappHelper();
|
||||
var helper = new sharedUtils.Helper();
|
||||
var client = new loop.StandaloneClient({
|
||||
baseServerUrl: loop.config.serverUrl
|
||||
});
|
||||
|
@ -797,7 +777,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
UnsupportedDeviceView: UnsupportedDeviceView,
|
||||
init: init,
|
||||
PromoteFirefoxView: PromoteFirefoxView,
|
||||
WebappHelper: WebappHelper,
|
||||
WebappRootView: WebappRootView
|
||||
};
|
||||
})(jQuery, _, window.OT, navigator.mozL10n);
|
||||
|
|
|
@ -15,7 +15,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
||||
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedViews = loop.shared.views;
|
||||
sharedViews = loop.shared.views,
|
||||
sharedUtils = loop.shared.utils;
|
||||
|
||||
/**
|
||||
* Homepage view.
|
||||
|
@ -435,7 +436,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
|
||||
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
|
||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||
.isRequired,
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
|
@ -690,7 +691,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
|
||||
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
|
||||
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
|
||||
.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.
|
||||
*/
|
||||
function init() {
|
||||
var helper = new WebappHelper();
|
||||
var helper = new sharedUtils.Helper();
|
||||
var client = new loop.StandaloneClient({
|
||||
baseServerUrl: loop.config.serverUrl
|
||||
});
|
||||
|
@ -797,7 +777,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
UnsupportedDeviceView: UnsupportedDeviceView,
|
||||
init: init,
|
||||
PromoteFirefoxView: PromoteFirefoxView,
|
||||
WebappHelper: WebappHelper,
|
||||
WebappRootView: WebappRootView
|
||||
};
|
||||
})(jQuery, _, window.OT, navigator.mozL10n);
|
||||
|
|
|
@ -9,10 +9,24 @@ var expect = chai.expect;
|
|||
describe("loop.conversation", function() {
|
||||
"use strict";
|
||||
|
||||
var ConversationRouter = loop.conversation.ConversationRouter,
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedView = loop.shared.views,
|
||||
sandbox,
|
||||
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() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
sandbox.useFakeTimers();
|
||||
|
@ -26,14 +40,14 @@ describe("loop.conversation", function() {
|
|||
get locale() {
|
||||
return "en-US";
|
||||
},
|
||||
setLoopCharPref: sandbox.stub(),
|
||||
getLoopCharPref: sandbox.stub(),
|
||||
getLoopBoolPref: sandbox.stub(),
|
||||
getCallData: sandbox.stub(),
|
||||
releaseCallData: function() {},
|
||||
startAlerting: function() {},
|
||||
stopAlerting: function() {},
|
||||
ensureRegistered: function() {},
|
||||
setLoopCharPref: sinon.stub(),
|
||||
getLoopCharPref: sinon.stub(),
|
||||
getLoopBoolPref: sinon.stub(),
|
||||
getCallData: sinon.stub(),
|
||||
releaseCallData: sinon.stub(),
|
||||
startAlerting: sinon.stub(),
|
||||
stopAlerting: sinon.stub(),
|
||||
ensureRegistered: sinon.stub(),
|
||||
get appVersionInfo() {
|
||||
return {
|
||||
version: "42",
|
||||
|
@ -57,21 +71,19 @@ describe("loop.conversation", function() {
|
|||
var oldTitle;
|
||||
|
||||
beforeEach(function() {
|
||||
oldTitle = document.title;
|
||||
|
||||
sandbox.stub(React, "renderComponent");
|
||||
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,
|
||||
"initialize");
|
||||
|
||||
sandbox.stub(Backbone.history, "start");
|
||||
window.OT = {
|
||||
overrideGuidStorage: sinon.stub()
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
document.title = oldTitle;
|
||||
delete window.OT;
|
||||
});
|
||||
|
||||
it("should initalize L10n", function() {
|
||||
|
@ -82,300 +94,256 @@ describe("loop.conversation", function() {
|
|||
navigator.mozLoop);
|
||||
});
|
||||
|
||||
it("should set the document title", function() {
|
||||
it("should create the IncomingConversationView", function() {
|
||||
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() {
|
||||
var conversation, client;
|
||||
describe("IncomingConversationView", function() {
|
||||
var conversation, client, icView, oldTitle;
|
||||
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
loop.conversation.IncomingConversationView({
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
notifications: notifications,
|
||||
sdk: {}
|
||||
}));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
oldTitle = document.title;
|
||||
client = new loop.Client();
|
||||
conversation = new loop.shared.models.ConversationModel({}, {
|
||||
sdk: {}
|
||||
});
|
||||
sandbox.spy(conversation, "setIncomingSessionData");
|
||||
conversation.set({callId: 42});
|
||||
sandbox.stub(conversation, "setOutgoingSessionData");
|
||||
});
|
||||
|
||||
describe("Routes", function() {
|
||||
var router;
|
||||
afterEach(function() {
|
||||
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() {
|
||||
router = new ConversationRouter({
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
notifications: notifications
|
||||
});
|
||||
sandbox.stub(conversation, "incoming");
|
||||
fakeSessionData = {
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
apiKey: "apiKey",
|
||||
callType: "callType",
|
||||
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
|
||||
// just pass in the sandbox and put somewhere generally usable
|
||||
sinon.assert.calledOnce(navigator.mozLoop.startAlerting);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
it("should call getCallData on navigator.mozLoop", function() {
|
||||
icView = mountTestComponent();
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(router, "loadReactComponent");
|
||||
stubComponent(loop.conversation, "IncomingCallView");
|
||||
});
|
||||
sinon.assert.calledOnce(navigator.mozLoop.getCallData);
|
||||
sinon.assert.calledWith(navigator.mozLoop.getCallData, 42);
|
||||
});
|
||||
|
||||
it("should start alerting", function() {
|
||||
sandbox.stub(navigator.mozLoop, "startAlerting");
|
||||
router.incoming("fakeVersion");
|
||||
|
||||
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("getCallData successful", function() {
|
||||
var promise, resolveWebSocketConnect,
|
||||
rejectWebSocketConnect;
|
||||
|
||||
describe("Session Data setup", function() {
|
||||
beforeEach(function() {
|
||||
fakeSessionData = {
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
apiKey: "apiKey",
|
||||
callType: "callType",
|
||||
callId: "Hello",
|
||||
progressURL: "http://progress.example.com",
|
||||
websocketToken: 123
|
||||
};
|
||||
|
||||
sandbox.stub(router, "_setupWebSocketAndCallView");
|
||||
|
||||
navigator.mozLoop.getCallData.returns(fakeSessionData);
|
||||
sandbox.stub(loop, "CallConnectionWebSocket").returns({
|
||||
promiseConnect: function () {
|
||||
promise = new Promise(function(resolve, reject) {
|
||||
resolveWebSocketConnect = resolve;
|
||||
rejectWebSocketConnect = reject;
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
on: sinon.stub()
|
||||
});
|
||||
});
|
||||
|
||||
it("should store the session data", function() {
|
||||
router.incoming("fakeVersion");
|
||||
sandbox.stub(conversation, "setIncomingSessionData");
|
||||
|
||||
icView = mountTestComponent();
|
||||
|
||||
sinon.assert.calledOnce(conversation.setIncomingSessionData);
|
||||
sinon.assert.calledWithExactly(conversation.setIncomingSessionData,
|
||||
fakeSessionData);
|
||||
});
|
||||
|
||||
it("should call #_setupWebSocketAndCallView", function() {
|
||||
it("should setup the websocket connection", function() {
|
||||
icView = mountTestComponent();
|
||||
|
||||
router.incoming("fakeVersion");
|
||||
|
||||
sinon.assert.calledOnce(router._setupWebSocketAndCallView);
|
||||
sinon.assert.calledWithExactly(router._setupWebSocketAndCallView);
|
||||
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
|
||||
sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
|
||||
callId: "Hello",
|
||||
url: "http://progress.example.com",
|
||||
websocketToken: "7b"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#_setupWebSocketAndCallView", function() {
|
||||
describe("WebSocket Handling", function() {
|
||||
beforeEach(function() {
|
||||
conversation.setIncomingSessionData({
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
apiKey: "apiKey",
|
||||
callType: "callType",
|
||||
callId: "Hello",
|
||||
progressURL: "http://progress.example.com",
|
||||
websocketToken: 123
|
||||
promise = new Promise(function(resolve, reject) {
|
||||
resolveWebSocketConnect = resolve;
|
||||
rejectWebSocketConnect = reject;
|
||||
});
|
||||
|
||||
sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
|
||||
});
|
||||
|
||||
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() {
|
||||
var promise;
|
||||
it("should display an error if the websocket failed to connect", function(done) {
|
||||
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() {
|
||||
sandbox.stub(loop, "CallConnectionWebSocket").returns({
|
||||
promiseConnect: function() {
|
||||
promise = new Promise(function(resolve, reject) {
|
||||
resolve();
|
||||
icView = mountTestComponent();
|
||||
promise = new Promise(function(resolve, reject) {
|
||||
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;
|
||||
},
|
||||
on: sinon.spy()
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||
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) {
|
||||
router._setupWebSocketAndCallView();
|
||||
describe("progress - terminated - timeout (previousState = alerting)", function() {
|
||||
it("should stop alerting", function(done) {
|
||||
promise.then(function() {
|
||||
icView._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "timeout"
|
||||
}, "alerting");
|
||||
|
||||
promise.then(function () {
|
||||
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
|
||||
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();
|
||||
});
|
||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("progress - terminated - timeout (previousState = alerting)", function() {
|
||||
it("should stop alerting", function(done) {
|
||||
promise.then(function() {
|
||||
router._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "timeout"
|
||||
}, "alerting");
|
||||
it("should close the websocket", function(done) {
|
||||
promise.then(function() {
|
||||
icView._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "timeout"
|
||||
}, "alerting");
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||
done();
|
||||
});
|
||||
sinon.assert.calledOnce(icView._websocket.close);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should close the websocket", function(done) {
|
||||
promise.then(function() {
|
||||
router._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "timeout"
|
||||
}, "alerting");
|
||||
it("should close the window", function(done) {
|
||||
promise.then(function() {
|
||||
icView._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "timeout"
|
||||
}, "alerting");
|
||||
|
||||
sinon.assert.calledOnce(router._websocket.close);
|
||||
done();
|
||||
});
|
||||
});
|
||||
sandbox.clock.tick(1);
|
||||
|
||||
it("should close the window", function(done) {
|
||||
promise.then(function() {
|
||||
router._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "timeout"
|
||||
}, "alerting");
|
||||
|
||||
sinon.assert.calledOnce(window.close);
|
||||
done();
|
||||
});
|
||||
sinon.assert.calledOnce(window.close);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -385,6 +353,7 @@ describe("loop.conversation", function() {
|
|||
|
||||
describe("#accept", function() {
|
||||
beforeEach(function() {
|
||||
icView = mountTestComponent();
|
||||
conversation.setIncomingSessionData({
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
|
@ -394,72 +363,38 @@ describe("loop.conversation", function() {
|
|||
progressURL: "http://progress.example.com",
|
||||
websocketToken: 123
|
||||
});
|
||||
router._setupWebSocketAndCallView();
|
||||
|
||||
sandbox.stub(router._websocket, "accept");
|
||||
sandbox.stub(navigator.mozLoop, "stopAlerting");
|
||||
sandbox.stub(icView._websocket, "accept");
|
||||
sandbox.stub(icView.props.conversation, "accepted");
|
||||
});
|
||||
|
||||
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() {
|
||||
router.accept();
|
||||
icView.accept();
|
||||
|
||||
sinon.assert.calledOnce(router._websocket.accept);
|
||||
sinon.assert.calledOnce(icView._websocket.accept);
|
||||
});
|
||||
|
||||
it("should stop alerting", function() {
|
||||
router.accept();
|
||||
icView.accept();
|
||||
|
||||
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() {
|
||||
beforeEach(function() {
|
||||
icView = mountTestComponent();
|
||||
|
||||
sandbox.stub(window, "close");
|
||||
router._websocket = {
|
||||
decline: sandbox.spy()
|
||||
icView._websocket = {
|
||||
decline: sinon.stub(),
|
||||
close: sinon.stub()
|
||||
};
|
||||
conversation.setIncomingSessionData({
|
||||
callId: 8699,
|
||||
|
@ -468,76 +403,40 @@ describe("loop.conversation", function() {
|
|||
});
|
||||
|
||||
it("should close the window", function() {
|
||||
router.decline();
|
||||
icView.decline();
|
||||
|
||||
sandbox.clock.tick(1);
|
||||
|
||||
sinon.assert.calledOnce(window.close);
|
||||
});
|
||||
|
||||
it("should stop alerting", function() {
|
||||
sandbox.stub(navigator.mozLoop, "stopAlerting");
|
||||
router.decline();
|
||||
icView.decline();
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||
});
|
||||
|
||||
it("should release callData", function() {
|
||||
sandbox.stub(navigator.mozLoop, "releaseCallData");
|
||||
router.decline();
|
||||
icView.decline();
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
|
||||
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() {
|
||||
beforeEach(function() {
|
||||
router._websocket = {
|
||||
decline: sandbox.spy()
|
||||
icView = mountTestComponent();
|
||||
|
||||
icView._websocket = {
|
||||
decline: sinon.spy(),
|
||||
close: sinon.stub()
|
||||
};
|
||||
sandbox.stub(window, "close");
|
||||
});
|
||||
|
||||
it("should call mozLoop.stopAlerting", function() {
|
||||
sandbox.stub(navigator.mozLoop, "stopAlerting");
|
||||
router.declineAndBlock();
|
||||
icView.declineAndBlock();
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
|
||||
});
|
||||
|
@ -547,7 +446,7 @@ describe("loop.conversation", function() {
|
|||
.returns("fakeToken");
|
||||
var deleteCallUrl = sandbox.stub(loop.Client.prototype,
|
||||
"deleteCallUrl");
|
||||
router.declineAndBlock();
|
||||
icView.declineAndBlock();
|
||||
|
||||
sinon.assert.calledOnce(deleteCallUrl);
|
||||
sinon.assert.calledWithExactly(deleteCallUrl, "fakeToken",
|
||||
|
@ -556,7 +455,7 @@ describe("loop.conversation", function() {
|
|||
|
||||
it("should get callToken from conversation model", function() {
|
||||
sandbox.stub(conversation, "get");
|
||||
router.declineAndBlock();
|
||||
icView.declineAndBlock();
|
||||
|
||||
sinon.assert.calledTwice(conversation.get);
|
||||
sinon.assert.calledWithExactly(conversation.get, "callToken");
|
||||
|
@ -572,14 +471,14 @@ describe("loop.conversation", function() {
|
|||
sandbox.stub(loop.Client.prototype, "deleteCallUrl", function(_, cb) {
|
||||
cb(fakeError);
|
||||
});
|
||||
router.declineAndBlock();
|
||||
icView.declineAndBlock();
|
||||
|
||||
sinon.assert.calledOnce(log);
|
||||
sinon.assert.calledWithExactly(log, fakeError);
|
||||
});
|
||||
|
||||
it("should close the window", function() {
|
||||
router.declineAndBlock();
|
||||
icView.declineAndBlock();
|
||||
|
||||
sandbox.clock.tick(1);
|
||||
|
||||
|
@ -589,63 +488,66 @@ describe("loop.conversation", function() {
|
|||
});
|
||||
|
||||
describe("Events", function() {
|
||||
var router, fakeSessionData;
|
||||
var fakeSessionData;
|
||||
|
||||
beforeEach(function() {
|
||||
icView = mountTestComponent();
|
||||
|
||||
fakeSessionData = {
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
apiKey: "apiKey"
|
||||
};
|
||||
sandbox.stub(loop.conversation.ConversationRouter.prototype,
|
||||
"navigate");
|
||||
conversation.set("loopToken", "fakeToken");
|
||||
router = new loop.conversation.ConversationRouter({
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
notifications: notifications
|
||||
});
|
||||
navigator.mozLoop.getLoopCharPref.returns("http://fake");
|
||||
stubComponent(sharedView, "ConversationView");
|
||||
});
|
||||
|
||||
it("should navigate to call/ongoing once the call is ready",
|
||||
function() {
|
||||
router.incoming(42);
|
||||
describe("call:accepted", function() {
|
||||
it("should display the ConversationView",
|
||||
function() {
|
||||
conversation.accepted();
|
||||
|
||||
conversation.incoming();
|
||||
|
||||
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");
|
||||
TestUtils.findRenderedComponentWithType(icView,
|
||||
sharedView.ConversationView);
|
||||
});
|
||||
});
|
||||
|
||||
it("should navigate to call/feedback when network disconnects",
|
||||
function() {
|
||||
conversation.trigger("session:network-disconnected");
|
||||
describe("session:ended", function() {
|
||||
it("should display the feedback view when the call session ends",
|
||||
function() {
|
||||
conversation.trigger("session:ended");
|
||||
|
||||
sinon.assert.calledOnce(router.navigate);
|
||||
sinon.assert.calledWith(router.navigate, "call/feedback");
|
||||
});
|
||||
TestUtils.findRenderedComponentWithType(icView,
|
||||
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() {
|
||||
beforeEach(function() {
|
||||
router._websocket = {
|
||||
icView._websocket = {
|
||||
mediaUp: sinon.spy()
|
||||
};
|
||||
router.incoming("fakeVersion");
|
||||
});
|
||||
|
||||
describe("publishStream", function() {
|
||||
|
@ -653,7 +555,7 @@ describe("loop.conversation", function() {
|
|||
function() {
|
||||
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" +
|
||||
|
@ -661,7 +563,7 @@ describe("loop.conversation", function() {
|
|||
conversation.set("subscribedStream", 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() {
|
||||
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" +
|
||||
|
@ -678,7 +580,7 @@ describe("loop.conversation", function() {
|
|||
conversation.set("publishedStream", 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/feedbackApiClient.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/views.js"></script>
|
||||
<script src="../../content/shared/js/websocket.js"></script>
|
||||
<script src="../../content/js/client.js"></script>
|
||||
<script src="../../content/js/desktopRouter.js"></script>
|
||||
<script src="../../content/js/conversation.js"></script>
|
||||
<script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
|
||||
<script src="../../content/js/panel.js"></script>
|
||||
|
|
|
@ -13,13 +13,6 @@ describe("loop.panel", function() {
|
|||
|
||||
var sandbox, notifications, fakeXHR, requests = [];
|
||||
|
||||
function createTestRouter(fakeDocument) {
|
||||
return new loop.panel.PanelRouter({
|
||||
notifications: notifications,
|
||||
document: fakeDocument
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
<script src="../../content/shared/js/models.js"></script>
|
||||
<script src="../../content/shared/js/mixins.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/feedbackApiClient.js"></script>
|
||||
|
||||
|
@ -46,7 +45,6 @@
|
|||
<script src="mixins_test.js"></script>
|
||||
<script src="utils_test.js"></script>
|
||||
<script src="views_test.js"></script>
|
||||
<script src="router_test.js"></script>
|
||||
<script src="websocket_test.js"></script>
|
||||
<script src="feedbackApiClient_test.js"></script>
|
||||
<script>
|
||||
|
|
|
@ -65,13 +65,13 @@ describe("loop.shared.models", function() {
|
|||
conversation.set("loopToken", "fakeToken");
|
||||
});
|
||||
|
||||
describe("#incoming", function() {
|
||||
it("should trigger a `call:incoming` event", function(done) {
|
||||
conversation.once("call:incoming", function() {
|
||||
describe("#accepted", function() {
|
||||
it("should trigger a `call:accepted` event", function(done) {
|
||||
conversation.once("call:accepted", function() {
|
||||
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();
|
||||
});
|
||||
|
||||
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() {
|
||||
afterEach(function() {
|
||||
navigator.mozLoop = undefined;
|
||||
|
|
|
@ -12,6 +12,7 @@ describe("loop.webapp", function() {
|
|||
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedViews = loop.shared.views,
|
||||
sharedUtils = loop.shared.utils,
|
||||
sandbox,
|
||||
notifications,
|
||||
feedbackApiClient;
|
||||
|
@ -33,7 +34,7 @@ describe("loop.webapp", function() {
|
|||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(React, "renderComponent");
|
||||
sandbox.stub(loop.webapp.WebappHelper.prototype,
|
||||
sandbox.stub(sharedUtils.Helper.prototype,
|
||||
"locationHash").returns("#call/fake-Token");
|
||||
loop.config.feedbackApiUrl = "http://fake.invalid";
|
||||
conversationSetStub =
|
||||
|
@ -78,7 +79,7 @@ describe("loop.webapp", function() {
|
|||
});
|
||||
conversation.set("loopToken", "fakeToken");
|
||||
ocView = mountTestComponent({
|
||||
helper: new loop.webapp.WebappHelper(),
|
||||
helper: new sharedUtils.Helper(),
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
notifications: notifications,
|
||||
|
@ -473,13 +474,13 @@ describe("loop.webapp", function() {
|
|||
});
|
||||
|
||||
describe("WebappRootView", function() {
|
||||
var webappHelper, sdk, conversationModel, client, props;
|
||||
var helper, sdk, conversationModel, client, props;
|
||||
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
loop.webapp.WebappRootView({
|
||||
client: client,
|
||||
helper: webappHelper,
|
||||
helper: helper,
|
||||
notifications: notifications,
|
||||
sdk: sdk,
|
||||
conversation: conversationModel,
|
||||
|
@ -488,7 +489,7 @@ describe("loop.webapp", function() {
|
|||
}
|
||||
|
||||
beforeEach(function() {
|
||||
webappHelper = new loop.webapp.WebappHelper();
|
||||
helper = new sharedUtils.Helper();
|
||||
sdk = {
|
||||
checkSystemRequirements: function() { return true; }
|
||||
};
|
||||
|
@ -505,7 +506,7 @@ describe("loop.webapp", function() {
|
|||
|
||||
it("should mount the unsupportedDevice view if the device is running iOS",
|
||||
function() {
|
||||
sandbox.stub(webappHelper, "isIOS").returns(true);
|
||||
sandbox.stub(helper, "isIOS").returns(true);
|
||||
|
||||
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/utils.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/views.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 type="text/javascript;version=1.8" src="../content/js/contacts.js"></script>
|
||||
<script>
|
||||
|
|
Загрузка…
Ссылка в новой задаче