From 826cfb318a9e513e05a0b398e9492e8070289c40 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Thu, 12 Mar 2015 14:01:38 +0000 Subject: [PATCH] Bug 1088672 - Part 7. Rewrite Loop's incoming call handling in the flux style. Remove the now redundant non-flux based code for incoming calls. r=mikedeboer --- .../components/loop/content/conversation.html | 1 - browser/components/loop/content/js/client.js | 31 -- .../loop/content/js/conversation.js | 1 - .../loop/content/js/conversation.jsx | 1 - .../loop/content/js/conversationAppStore.js | 7 +- .../loop/content/js/conversationViews.js | 325 ----------- .../loop/content/js/conversationViews.jsx | 325 ----------- .../loop/test/desktop-local/client_test.js | 36 -- .../conversationAppStore_test.js | 3 +- .../desktop-local/conversationViews_test.js | 522 ------------------ 10 files changed, 2 insertions(+), 1250 deletions(-) diff --git a/browser/components/loop/content/conversation.html b/browser/components/loop/content/conversation.html index 5891e130eec4..1af5d33365bf 100644 --- a/browser/components/loop/content/conversation.html +++ b/browser/components/loop/content/conversation.html @@ -26,7 +26,6 @@ - diff --git a/browser/components/loop/content/js/client.js b/browser/components/loop/content/js/client.js index 9b6a0b6e42bf..439c5ada6f04 100644 --- a/browser/components/loop/content/js/client.js +++ b/browser/components/loop/content/js/client.js @@ -75,37 +75,6 @@ loop.Client = (function($) { cb(error); }, - /** - * Block call URL based on the token identifier - * - * @param {string} token Conversation identifier used to block the URL - * @param {mozLoop.LOOP_SESSION_TYPE} sessionType The type of session which - * the url belongs to. - * @param {function} cb Callback function used for handling an error - * response. XXX The incoming call panel does not - * exist after the block button is clicked therefore - * it does not make sense to display an error. - **/ - deleteCallUrl: function(token, sessionType, cb) { - function deleteRequestCallback(error, responseText) { - if (error) { - this._failureHandler(cb, error); - return; - } - - try { - cb(null); - } catch (err) { - console.log("Error deleting call info", err); - cb(err); - } - } - - this.mozLoop.hawkRequest(sessionType, - "/call-url/" + token, "DELETE", null, - deleteRequestCallback.bind(this)); - }, - /** * Sets up an outgoing call, getting the relevant data from the server. * diff --git a/browser/components/loop/content/js/conversation.js b/browser/components/loop/content/js/conversation.js index 2fcf6c98cb06..46010fcd98a8 100644 --- a/browser/components/loop/content/js/conversation.js +++ b/browser/components/loop/content/js/conversation.js @@ -16,7 +16,6 @@ loop.conversation = (function(mozL10n) { var sharedModels = loop.shared.models; var sharedActions = loop.shared.actions; - var IncomingConversationView = loop.conversationViews.IncomingConversationView; var CallControllerView = loop.conversationViews.CallControllerView; var CallIdentifierView = loop.conversationViews.CallIdentifierView; var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView; diff --git a/browser/components/loop/content/js/conversation.jsx b/browser/components/loop/content/js/conversation.jsx index 2dfd622bf787..8fefc1c6cfbb 100644 --- a/browser/components/loop/content/js/conversation.jsx +++ b/browser/components/loop/content/js/conversation.jsx @@ -16,7 +16,6 @@ loop.conversation = (function(mozL10n) { var sharedModels = loop.shared.models; var sharedActions = loop.shared.actions; - var IncomingConversationView = loop.conversationViews.IncomingConversationView; var CallControllerView = loop.conversationViews.CallControllerView; var CallIdentifierView = loop.conversationViews.CallIdentifierView; var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView; diff --git a/browser/components/loop/content/js/conversationAppStore.js b/browser/components/loop/content/js/conversationAppStore.js index 92be028a86d9..ca30dee98bd2 100644 --- a/browser/components/loop/content/js/conversationAppStore.js +++ b/browser/components/loop/content/js/conversationAppStore.js @@ -69,12 +69,7 @@ loop.store.ConversationAppStore = (function() { return; } - // XXX windowData is a hack for the IncomingConversationView until - // we rework it for the flux model in bug 1088672. - this.setStoreState({ - windowType: windowData.type, - windowData: windowData - }); + this.setStoreState({windowType: windowData.type}); this._dispatcher.dispatch(new loop.shared.actions.SetupWindowData(_.extend({ windowId: actionData.windowId}, windowData))); diff --git a/browser/components/loop/content/js/conversationViews.js b/browser/components/loop/content/js/conversationViews.js index a6e5067a5f0a..7a8d8739a923 100644 --- a/browser/components/loop/content/js/conversationViews.js +++ b/browser/components/loop/content/js/conversationViews.js @@ -314,9 +314,6 @@ loop.conversationViews = (function(mozL10n) { /** * Something went wrong view. Displayed when there's a big problem. - * - * XXX Based on CallFailedView, but built specially until we flux-ify the - * incoming call views (bug 1088672). */ var GenericFailureView = React.createClass({displayName: "GenericFailureView", mixins: [sharedMixins.AudioMixin], @@ -347,327 +344,6 @@ loop.conversationViews = (function(mozL10n) { } }); - /** - * This view manages the incoming conversation views - from - * call initiation through to the actual conversation and call end. - * - * At the moment, it does more than that, these parts need refactoring out. - */ - var IncomingConversationView = React.createClass({displayName: "IncomingConversationView", - mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin], - - propTypes: { - client: React.PropTypes.instanceOf(loop.Client).isRequired, - conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel) - .isRequired, - sdk: React.PropTypes.object.isRequired, - isDesktop: React.PropTypes.bool, - conversationAppStore: React.PropTypes.instanceOf( - loop.store.ConversationAppStore).isRequired - }, - - getDefaultProps: function() { - return { - isDesktop: false - }; - }, - - getInitialState: function() { - return { - callFailed: false, // XXX this should be removed when bug 1047410 lands. - 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 ( - React.createElement(AcceptCallView, { - model: this.props.conversation, - video: this.props.conversation.hasVideoStream("incoming")} - ) - ); - } - case "connected": { - document.title = this.props.conversation.getCallIdentifier(); - - var callType = this.props.conversation.get("selectedCallType"); - - return ( - React.createElement(sharedViews.ConversationView, { - isDesktop: this.props.isDesktop, - initiate: true, - sdk: this.props.sdk, - model: this.props.conversation, - video: {enabled: callType !== "audio"}} - ) - ); - } - case "end": { - // XXX To be handled with the "failed" view state when bug 1047410 lands - if (this.state.callFailed) { - return React.createElement(GenericFailureView, { - cancelCall: this.closeWindow.bind(this)} - ); - } - - document.title = mozL10n.get("conversation_has_ended"); - - this.play("terminated"); - - return ( - React.createElement(sharedViews.FeedbackView, { - onAfterFeedbackReceived: this.closeWindow.bind(this)} - ) - ); - } - case "close": { - this.closeWindow(); - return (React.createElement("div", null)); - } - } - }, - - /** - * Notify the user that the connection was not possible - * @param {{code: number, message: string}} error - */ - _notifyError: function(error) { - // XXX Not the ideal response, but bug 1047410 will be replacing - // this by better "call failed" UI. - console.error(error); - this.setState({callFailed: true, callStatus: "end"}); - }, - - /** - * Peer hung up. Notifies the user and ends the call. - * - * Event properties: - * - {String} connectionId: OT session id - */ - _onPeerHungup: function() { - this.setState({callFailed: false, callStatus: "end"}); - }, - - /** - * Network disconnected. Notifies the user and ends the call. - */ - _onNetworkDisconnected: function() { - // XXX Not the ideal response, but bug 1047410 will be replacing - // this by better "call failed" UI. - this.setState({callFailed: true, callStatus: "end"}); - }, - - /** - * Incoming call route. - */ - setupIncomingCall: function() { - navigator.mozLoop.startAlerting(); - - // XXX This is a hack until we rework for the flux model in bug 1088672. - var callData = this.props.conversationAppStore.getStoreState().windowData; - - 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.calls.clearCallInProgress( - this.props.conversation.get("windowId")); - this.setState({callStatus: "end"}); - }, - - /** - * Used to set up the web socket connection and navigate to the - * call view if appropriate. - */ - _setupWebSocket: function() { - this._websocket = new loop.CallConnectionWebSocket({ - url: this.props.conversation.get("progressURL"), - websocketToken: this.props.conversation.get("websocketToken"), - callId: this.props.conversation.get("callId"), - }); - this._websocket.promiseConnect().then(function(progressStatus) { - this.setState({ - callStatus: progressStatus === "terminated" ? "close" : "incoming" - }); - }.bind(this), function() { - this._handleSessionError(); - return; - }.bind(this)); - - this._websocket.on("progress", this._handleWebSocketProgress, this); - }, - - /** - * Checks if the streams have been connected, and notifies the - * websocket that the media is now connected. - */ - _checkConnected: function() { - // Check we've had both local and remote streams connected before - // sending the media up message. - if (this.props.conversation.streamsConnected()) { - this._websocket.mediaUp(); - } - }, - - /** - * Used to receive websocket progress and to determine how to handle - * it if appropraite. - * If we add more cases here, then we should refactor this function. - * - * @param {Object} progressData The progress data from the websocket. - * @param {String} previousState The previous state from the websocket. - */ - _handleWebSocketProgress: function(progressData, previousState) { - // We only care about the terminated state at the moment. - if (progressData.state !== "terminated") - return; - - // XXX This would be nicer in the _abortIncomingCall function, but we need to stop - // it here for now due to server-side issues that are being fixed in bug 1088351. - // This is before the abort call to ensure that it happens before the window is - // closed. - navigator.mozLoop.stopAlerting(); - - // If we hit any of the termination reasons, and the user hasn't accepted - // then it seems reasonable to close the window/abort the incoming call. - // - // If the user has accepted the call, and something's happened, display - // the call failed view. - // - // https://wiki.mozilla.org/Loop/Architecture/MVP#Termination_Reasons - if (previousState === "init" || previousState === "alerting") { - this._abortIncomingCall(); - } else { - this.setState({callFailed: true, callStatus: "end"}); - } - - }, - - /** - * Silently aborts an incoming call - stops the alerting, and - * closes the websocket. - */ - _abortIncomingCall: function() { - 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); - }, - - /** - * Accepts an incoming call. - */ - accept: function() { - navigator.mozLoop.stopAlerting(); - this._websocket.accept(); - this.props.conversation.accepted(); - }, - - /** - * Declines a call and handles closing of the window. - */ - _declineCall: function() { - this._websocket.decline(); - navigator.mozLoop.calls.clearCallInProgress( - this.props.conversation.get("windowId")); - 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); - }, - - /** - * Declines an incoming call. - */ - decline: function() { - navigator.mozLoop.stopAlerting(); - this._declineCall(); - }, - - /** - * Decline and block an incoming call - * @note: - * - loopToken is the callUrl identifier. It gets set in the panel - * after a callUrl is received - */ - declineAndBlock: function() { - navigator.mozLoop.stopAlerting(); - var token = this.props.conversation.get("callToken"); - var callerId = this.props.conversation.get("callerId"); - - // If this is a direct call, we'll need to block the caller directly. - if (callerId && EMAIL_OR_PHONE_RE.test(callerId)) { - navigator.mozLoop.calls.blockDirectCaller(callerId, function(err) { - // 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 1103150). - console.log(err.fileName + ":" + err.lineNumber + ": " + err.message); - }); - } else { - this.props.client.deleteCallUrl(token, - this.props.conversation.get("sessionType"), - 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). - console.log(error); - }); - } - - this._declineCall(); - }, - - /** - * 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. - console.error("Failed initiating the call session."); - }, - }); - /** * View for pending conversations. Displays a cancel button and appropriate * pending/ringing strings. @@ -1083,7 +759,6 @@ loop.conversationViews = (function(mozL10n) { _getContactDisplayName: _getContactDisplayName, GenericFailureView: GenericFailureView, AcceptCallView: AcceptCallView, - IncomingConversationView: IncomingConversationView, OngoingConversationView: OngoingConversationView, CallControllerView: CallControllerView }; diff --git a/browser/components/loop/content/js/conversationViews.jsx b/browser/components/loop/content/js/conversationViews.jsx index cf6455e557c9..6688b002383d 100644 --- a/browser/components/loop/content/js/conversationViews.jsx +++ b/browser/components/loop/content/js/conversationViews.jsx @@ -314,9 +314,6 @@ loop.conversationViews = (function(mozL10n) { /** * Something went wrong view. Displayed when there's a big problem. - * - * XXX Based on CallFailedView, but built specially until we flux-ify the - * incoming call views (bug 1088672). */ var GenericFailureView = React.createClass({ mixins: [sharedMixins.AudioMixin], @@ -347,327 +344,6 @@ loop.conversationViews = (function(mozL10n) { } }); - /** - * This view manages the incoming conversation views - from - * call initiation through to the actual conversation and call end. - * - * At the moment, it does more than that, these parts need refactoring out. - */ - var IncomingConversationView = React.createClass({ - mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin], - - propTypes: { - client: React.PropTypes.instanceOf(loop.Client).isRequired, - conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel) - .isRequired, - sdk: React.PropTypes.object.isRequired, - isDesktop: React.PropTypes.bool, - conversationAppStore: React.PropTypes.instanceOf( - loop.store.ConversationAppStore).isRequired - }, - - getDefaultProps: function() { - return { - isDesktop: false - }; - }, - - getInitialState: function() { - return { - callFailed: false, // XXX this should be removed when bug 1047410 lands. - 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 ( - - ); - } - case "connected": { - document.title = this.props.conversation.getCallIdentifier(); - - var callType = this.props.conversation.get("selectedCallType"); - - return ( - - ); - } - case "end": { - // XXX To be handled with the "failed" view state when bug 1047410 lands - if (this.state.callFailed) { - return ; - } - - document.title = mozL10n.get("conversation_has_ended"); - - this.play("terminated"); - - return ( - - ); - } - case "close": { - this.closeWindow(); - return (
); - } - } - }, - - /** - * Notify the user that the connection was not possible - * @param {{code: number, message: string}} error - */ - _notifyError: function(error) { - // XXX Not the ideal response, but bug 1047410 will be replacing - // this by better "call failed" UI. - console.error(error); - this.setState({callFailed: true, callStatus: "end"}); - }, - - /** - * Peer hung up. Notifies the user and ends the call. - * - * Event properties: - * - {String} connectionId: OT session id - */ - _onPeerHungup: function() { - this.setState({callFailed: false, callStatus: "end"}); - }, - - /** - * Network disconnected. Notifies the user and ends the call. - */ - _onNetworkDisconnected: function() { - // XXX Not the ideal response, but bug 1047410 will be replacing - // this by better "call failed" UI. - this.setState({callFailed: true, callStatus: "end"}); - }, - - /** - * Incoming call route. - */ - setupIncomingCall: function() { - navigator.mozLoop.startAlerting(); - - // XXX This is a hack until we rework for the flux model in bug 1088672. - var callData = this.props.conversationAppStore.getStoreState().windowData; - - 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.calls.clearCallInProgress( - this.props.conversation.get("windowId")); - this.setState({callStatus: "end"}); - }, - - /** - * Used to set up the web socket connection and navigate to the - * call view if appropriate. - */ - _setupWebSocket: function() { - this._websocket = new loop.CallConnectionWebSocket({ - url: this.props.conversation.get("progressURL"), - websocketToken: this.props.conversation.get("websocketToken"), - callId: this.props.conversation.get("callId"), - }); - this._websocket.promiseConnect().then(function(progressStatus) { - this.setState({ - callStatus: progressStatus === "terminated" ? "close" : "incoming" - }); - }.bind(this), function() { - this._handleSessionError(); - return; - }.bind(this)); - - this._websocket.on("progress", this._handleWebSocketProgress, this); - }, - - /** - * Checks if the streams have been connected, and notifies the - * websocket that the media is now connected. - */ - _checkConnected: function() { - // Check we've had both local and remote streams connected before - // sending the media up message. - if (this.props.conversation.streamsConnected()) { - this._websocket.mediaUp(); - } - }, - - /** - * Used to receive websocket progress and to determine how to handle - * it if appropraite. - * If we add more cases here, then we should refactor this function. - * - * @param {Object} progressData The progress data from the websocket. - * @param {String} previousState The previous state from the websocket. - */ - _handleWebSocketProgress: function(progressData, previousState) { - // We only care about the terminated state at the moment. - if (progressData.state !== "terminated") - return; - - // XXX This would be nicer in the _abortIncomingCall function, but we need to stop - // it here for now due to server-side issues that are being fixed in bug 1088351. - // This is before the abort call to ensure that it happens before the window is - // closed. - navigator.mozLoop.stopAlerting(); - - // If we hit any of the termination reasons, and the user hasn't accepted - // then it seems reasonable to close the window/abort the incoming call. - // - // If the user has accepted the call, and something's happened, display - // the call failed view. - // - // https://wiki.mozilla.org/Loop/Architecture/MVP#Termination_Reasons - if (previousState === "init" || previousState === "alerting") { - this._abortIncomingCall(); - } else { - this.setState({callFailed: true, callStatus: "end"}); - } - - }, - - /** - * Silently aborts an incoming call - stops the alerting, and - * closes the websocket. - */ - _abortIncomingCall: function() { - 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); - }, - - /** - * Accepts an incoming call. - */ - accept: function() { - navigator.mozLoop.stopAlerting(); - this._websocket.accept(); - this.props.conversation.accepted(); - }, - - /** - * Declines a call and handles closing of the window. - */ - _declineCall: function() { - this._websocket.decline(); - navigator.mozLoop.calls.clearCallInProgress( - this.props.conversation.get("windowId")); - 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); - }, - - /** - * Declines an incoming call. - */ - decline: function() { - navigator.mozLoop.stopAlerting(); - this._declineCall(); - }, - - /** - * Decline and block an incoming call - * @note: - * - loopToken is the callUrl identifier. It gets set in the panel - * after a callUrl is received - */ - declineAndBlock: function() { - navigator.mozLoop.stopAlerting(); - var token = this.props.conversation.get("callToken"); - var callerId = this.props.conversation.get("callerId"); - - // If this is a direct call, we'll need to block the caller directly. - if (callerId && EMAIL_OR_PHONE_RE.test(callerId)) { - navigator.mozLoop.calls.blockDirectCaller(callerId, function(err) { - // 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 1103150). - console.log(err.fileName + ":" + err.lineNumber + ": " + err.message); - }); - } else { - this.props.client.deleteCallUrl(token, - this.props.conversation.get("sessionType"), - 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). - console.log(error); - }); - } - - this._declineCall(); - }, - - /** - * 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. - console.error("Failed initiating the call session."); - }, - }); - /** * View for pending conversations. Displays a cancel button and appropriate * pending/ringing strings. @@ -1083,7 +759,6 @@ loop.conversationViews = (function(mozL10n) { _getContactDisplayName: _getContactDisplayName, GenericFailureView: GenericFailureView, AcceptCallView: AcceptCallView, - IncomingConversationView: IncomingConversationView, OngoingConversationView: OngoingConversationView, CallControllerView: CallControllerView }; diff --git a/browser/components/loop/test/desktop-local/client_test.js b/browser/components/loop/test/desktop-local/client_test.js index 42b5a45cf057..475fb0184546 100644 --- a/browser/components/loop/test/desktop-local/client_test.js +++ b/browser/components/loop/test/desktop-local/client_test.js @@ -52,42 +52,6 @@ describe("loop.Client", function() { }); describe("loop.Client", function() { - describe("#deleteCallUrl", function() { - it("should make a delete call to /call-url/{fakeToken}", function() { - client.deleteCallUrl(fakeToken, mozLoop.LOOP_SESSION_TYPE.GUEST, callback); - - sinon.assert.calledOnce(hawkRequestStub); - sinon.assert.calledWith(hawkRequestStub, - mozLoop.LOOP_SESSION_TYPE.GUEST, - "/call-url/" + fakeToken, "DELETE"); - }); - - it("should call the callback with null when the request succeeds", - function() { - - // Sets up the hawkRequest stub to trigger the callback with no error - // and the url. - hawkRequestStub.callsArgWith(4, null); - - client.deleteCallUrl(fakeToken, mozLoop.LOOP_SESSION_TYPE.FXA, callback); - - sinon.assert.calledWithExactly(callback, null); - }); - - it("should send an error when the request fails", function() { - // Sets up the hawkRequest stub to trigger the callback with - // an error - hawkRequestStub.callsArgWith(4, fakeErrorRes); - - client.deleteCallUrl(fakeToken, mozLoop.LOOP_SESSION_TYPE.FXA, callback); - - sinon.assert.calledOnce(callback); - sinon.assert.calledWithMatch(callback, sinon.match(function(err) { - return err.code == 400 && "invalid token" == err.message; - })); - }); - }); - describe("#setupOutgoingCall", function() { var calleeIds, callType; diff --git a/browser/components/loop/test/desktop-local/conversationAppStore_test.js b/browser/components/loop/test/desktop-local/conversationAppStore_test.js index 5a3e35ab0179..6811b8ad7218 100644 --- a/browser/components/loop/test/desktop-local/conversationAppStore_test.js +++ b/browser/components/loop/test/desktop-local/conversationAppStore_test.js @@ -63,8 +63,7 @@ describe("loop.store.ConversationAppStore", function () { dispatcher.dispatch(new sharedActions.GetWindowData(fakeGetWindowData)); expect(store.getStoreState()).eql({ - windowType: "incoming", - windowData: fakeWindowData + windowType: "incoming" }); }); diff --git a/browser/components/loop/test/desktop-local/conversationViews_test.js b/browser/components/loop/test/desktop-local/conversationViews_test.js index afef74ec2aec..c3bf26e450e9 100644 --- a/browser/components/loop/test/desktop-local/conversationViews_test.js +++ b/browser/components/loop/test/desktop-local/conversationViews_test.js @@ -718,528 +718,6 @@ describe("loop.conversationViews", function () { }); }); - describe("IncomingConversationView", function() { - var conversationAppStore, conversation, client, icView, oldTitle, - feedbackStore; - - function mountTestComponent() { - return TestUtils.renderIntoDocument( - React.createElement(loop.conversationViews.IncomingConversationView, { - client: client, - conversation: conversation, - sdk: {}, - conversationAppStore: conversationAppStore - })); - } - - beforeEach(function() { - oldTitle = document.title; - client = new loop.Client(); - conversation = new loop.shared.models.ConversationModel({}, { - sdk: {} - }); - conversation.set({windowId: 42}); - var dispatcher = new loop.Dispatcher(); - conversationAppStore = new loop.store.ConversationAppStore({ - dispatcher: dispatcher, - mozLoop: navigator.mozLoop - }); - feedbackStore = new loop.store.FeedbackStore(dispatcher, { - feedbackClient: {} - }); - sandbox.stub(conversation, "setOutgoingSessionData"); - }); - - afterEach(function() { - icView = undefined; - document.title = oldTitle; - }); - - describe("start", function() { - it("should set the title to incoming_call_title2", function() { - conversationAppStore.setStoreState({ - windowData: { - progressURL: "fake", - websocketToken: "fake", - callId: 42 - } - }); - - icView = mountTestComponent(); - - expect(document.title).eql("incoming_call_title2"); - }); - }); - - describe("componentDidMount", function() { - var fakeSessionData, promise, resolveWebSocketConnect; - var rejectWebSocketConnect; - - beforeEach(function() { - fakeSessionData = { - sessionId: "sessionId", - sessionToken: "sessionToken", - apiKey: "apiKey", - callType: "callType", - callId: "Hello", - progressURL: "http://progress.example.com", - websocketToken: "7b" - }; - - conversationAppStore.setStoreState({ - windowData: fakeSessionData - }); - - stubComponent(loop.conversationViews, "AcceptCallView"); - stubComponent(sharedView, "ConversationView"); - }); - - it("should start alerting", function() { - icView = mountTestComponent(); - - sinon.assert.calledOnce(navigator.mozLoop.startAlerting); - }); - - describe("Session Data setup", function() { - beforeEach(function() { - 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() { - sandbox.stub(conversation, "setIncomingSessionData"); - - icView = mountTestComponent(); - - sinon.assert.calledOnce(conversation.setIncomingSessionData); - sinon.assert.calledWithExactly(conversation.setIncomingSessionData, - fakeSessionData); - }); - - it("should setup the websocket connection", function() { - icView = mountTestComponent(); - - sinon.assert.calledOnce(loop.CallConnectionWebSocket); - sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, { - callId: "Hello", - url: "http://progress.example.com", - websocketToken: "7b" - }); - }); - }); - - describe("WebSocket Handling", function() { - beforeEach(function() { - 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("incoming"); - - promise.then(function () { - expect(icView.state.callStatus).eql("incoming"); - done(); - }); - }); - - it("should set the state to close on success if the progress " + - "state is terminated", function(done) { - icView = mountTestComponent(); - resolveWebSocketConnect("terminated"); - - promise.then(function () { - expect(icView.state.callStatus).eql("close"); - done(); - }); - }); - - // XXX implement me as part of bug 1047410 - // see https://hg.mozilla.org/integration/fx-team/rev/5d2c69ebb321#l18.259 - it.skip("should should switch view state to failed", function(done) { - icView = mountTestComponent(); - rejectWebSocketConnect(); - - promise.then(function() {}, function() { - done(); - }); - }); - }); - - describe("WebSocket Events", function() { - describe("Call cancelled or timed out before acceptance", function() { - beforeEach(function() { - // Mounting the test component automatically calls the required - // setup functions - icView = mountTestComponent(); - promise = new Promise(function(resolve, reject) { - resolve(); - }); - - sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise); - sandbox.stub(loop.CallConnectionWebSocket.prototype, "close"); - }); - - describe("progress - terminated (previousState = alerting)", function() { - it("should stop alerting", function(done) { - promise.then(function() { - icView._websocket.trigger("progress", { - state: "terminated", - reason: WEBSOCKET_REASONS.TIMEOUT - }, "alerting"); - - sinon.assert.calledOnce(navigator.mozLoop.stopAlerting); - done(); - }); - }); - - it("should close the websocket", function(done) { - promise.then(function() { - icView._websocket.trigger("progress", { - state: "terminated", - reason: WEBSOCKET_REASONS.CLOSED - }, "alerting"); - - sinon.assert.calledOnce(icView._websocket.close); - done(); - }); - }); - - it("should close the window", function(done) { - promise.then(function() { - icView._websocket.trigger("progress", { - state: "terminated", - reason: WEBSOCKET_REASONS.ANSWERED_ELSEWHERE - }, "alerting"); - - sandbox.clock.tick(1); - - sinon.assert.calledOnce(fakeWindow.close); - done(); - }); - }); - }); - - - describe("progress - terminated (previousState not init" + - " nor alerting)", - function() { - it("should set the state to end", function(done) { - promise.then(function() { - icView._websocket.trigger("progress", { - state: "terminated", - reason: WEBSOCKET_REASONS.MEDIA_FAIL - }, "connecting"); - - expect(icView.state.callStatus).eql("end"); - done(); - }); - }); - - it("should stop alerting", function(done) { - promise.then(function() { - icView._websocket.trigger("progress", { - state: "terminated", - reason: WEBSOCKET_REASONS.MEDIA_FAIL - }, "connecting"); - - sinon.assert.calledOnce(navigator.mozLoop.stopAlerting); - done(); - }); - }); - }); - }); - }); - - describe("#accept", function() { - beforeEach(function() { - icView = mountTestComponent(); - conversation.setIncomingSessionData({ - sessionId: "sessionId", - sessionToken: "sessionToken", - apiKey: "apiKey", - callType: "callType", - callId: "Hello", - progressURL: "http://progress.example.com", - websocketToken: 123 - }); - - sandbox.stub(icView._websocket, "accept"); - sandbox.stub(icView.props.conversation, "accepted"); - }); - - it("should initiate the conversation", function() { - icView.accept(); - - sinon.assert.calledOnce(icView.props.conversation.accepted); - }); - - it("should notify the websocket of the user acceptance", function() { - icView.accept(); - - sinon.assert.calledOnce(icView._websocket.accept); - }); - - it("should stop alerting", function() { - icView.accept(); - - sinon.assert.calledOnce(navigator.mozLoop.stopAlerting); - }); - }); - - describe("#decline", function() { - beforeEach(function() { - icView = mountTestComponent(); - - icView._websocket = { - decline: sinon.stub(), - close: sinon.stub() - }; - conversation.set({ - windowId: "8699" - }); - conversation.setIncomingSessionData({ - websocketToken: 123 - }); - }); - - it("should close the window", function() { - icView.decline(); - - sandbox.clock.tick(1); - - sinon.assert.calledOnce(fakeWindow.close); - }); - - it("should stop alerting", function() { - icView.decline(); - - sinon.assert.calledOnce(navigator.mozLoop.stopAlerting); - }); - - it("should release callData", function() { - icView.decline(); - - sinon.assert.calledOnce(navigator.mozLoop.calls.clearCallInProgress); - sinon.assert.calledWithExactly( - navigator.mozLoop.calls.clearCallInProgress, "8699"); - }); - }); - - describe("#blocked", function() { - var mozLoop, deleteCallUrlStub; - - beforeEach(function() { - icView = mountTestComponent(); - - icView._websocket = { - decline: sinon.spy(), - close: sinon.stub() - }; - - mozLoop = { - LOOP_SESSION_TYPE: { - GUEST: 1, - FXA: 2 - } - }; - - deleteCallUrlStub = sandbox.stub(loop.Client.prototype, - "deleteCallUrl"); - }); - - it("should call mozLoop.stopAlerting", function() { - icView.declineAndBlock(); - - sinon.assert.calledOnce(navigator.mozLoop.stopAlerting); - }); - - it("should call delete call", function() { - sandbox.stub(conversation, "get").withArgs("callToken") - .returns("fakeToken") - .withArgs("sessionType") - .returns(mozLoop.LOOP_SESSION_TYPE.FXA); - - icView.declineAndBlock(); - - sinon.assert.calledOnce(deleteCallUrlStub); - sinon.assert.calledWithExactly(deleteCallUrlStub, - "fakeToken", mozLoop.LOOP_SESSION_TYPE.FXA, sinon.match.func); - }); - - it("should get callToken from conversation model", function() { - sandbox.stub(conversation, "get"); - icView.declineAndBlock(); - - sinon.assert.called(conversation.get); - sinon.assert.calledWithExactly(conversation.get, "callToken"); - sinon.assert.calledWithExactly(conversation.get, "windowId"); - }); - - it("should trigger error handling in case of error", function() { - // XXX just logging to console for now - var log = sandbox.stub(console, "log"); - var fakeError = { - error: true - }; - deleteCallUrlStub.callsArgWith(2, fakeError); - icView.declineAndBlock(); - - sinon.assert.calledOnce(log); - sinon.assert.calledWithExactly(log, fakeError); - }); - - it("should close the window", function() { - icView.declineAndBlock(); - - sandbox.clock.tick(1); - - sinon.assert.calledOnce(fakeWindow.close); - }); - }); - }); - - describe("Events", function() { - var fakeSessionData; - - beforeEach(function() { - - fakeSessionData = { - sessionId: "sessionId", - sessionToken: "sessionToken", - apiKey: "apiKey" - }; - - conversationAppStore.setStoreState({ - windowData: fakeSessionData - }); - - sandbox.stub(conversation, "setIncomingSessionData"); - sandbox.stub(loop, "CallConnectionWebSocket").returns({ - promiseConnect: function() { - return new Promise(function() {}); - }, - on: sandbox.spy() - }); - - icView = mountTestComponent(); - - conversation.set("loopToken", "fakeToken"); - stubComponent(sharedView, "ConversationView"); - }); - - describe("call:accepted", function() { - it("should display the ConversationView", - function() { - conversation.accepted(); - - TestUtils.findRenderedComponentWithType(icView, - sharedView.ConversationView); - }); - - it("should set the title to the call identifier", function() { - sandbox.stub(conversation, "getCallIdentifier").returns("fakeId"); - - conversation.accepted(); - - expect(document.title).eql("fakeId"); - }); - }); - - describe("session:ended", function() { - it("should display the feedback view when the call session ends", - function() { - conversation.trigger("session:ended"); - - 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:network-disconnected", function() { - it("should navigate to call failed when network disconnects", - function() { - conversation.trigger("session:network-disconnected"); - - TestUtils.findRenderedComponentWithType(icView, - loop.conversationViews.GenericFailureView); - }); - - it("should update the conversation window toolbar title", - function() { - conversation.trigger("session:network-disconnected"); - - expect(document.title).eql("generic_failure_title"); - }); - }); - - describe("Published and Subscribed Streams", function() { - beforeEach(function() { - icView._websocket = { - mediaUp: sinon.spy() - }; - }); - - describe("publishStream", function() { - it("should not notify the websocket if only one stream is up", - function() { - conversation.set("publishedStream", true); - - sinon.assert.notCalled(icView._websocket.mediaUp); - }); - - it("should notify the websocket that media is up if both streams" + - "are connected", function() { - conversation.set("subscribedStream", true); - conversation.set("publishedStream", true); - - sinon.assert.calledOnce(icView._websocket.mediaUp); - }); - }); - - describe("subscribedStream", function() { - it("should not notify the websocket if only one stream is up", - function() { - conversation.set("subscribedStream", true); - - sinon.assert.notCalled(icView._websocket.mediaUp); - }); - - it("should notify the websocket that media is up if both streams" + - "are connected", function() { - conversation.set("publishedStream", true); - conversation.set("subscribedStream", true); - - sinon.assert.calledOnce(icView._websocket.mediaUp); - }); - }); - }); - }); - }); - describe("AcceptCallView", function() { var view;