mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 14:45:29 +00:00
Bug 972017 Part 3 - Finish the view flow transition for direct calling for Loop. r=nperriault
This commit is contained in:
parent
ad583709b0
commit
85b2d189ea
@ -483,7 +483,8 @@ loop.conversation = (function(mozL10n) {
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
|
||||
// XXX New types for OutgoingConversationView
|
||||
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired
|
||||
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@ -504,7 +505,8 @@ loop.conversation = (function(mozL10n) {
|
||||
|
||||
if (this.state.outgoing) {
|
||||
return (OutgoingConversationView({
|
||||
store: this.props.store}
|
||||
store: this.props.store,
|
||||
dispatcher: this.props.dispatcher}
|
||||
));
|
||||
}
|
||||
|
||||
@ -517,7 +519,7 @@ loop.conversation = (function(mozL10n) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Panel initialisation.
|
||||
* Conversation initialisation.
|
||||
*/
|
||||
function init() {
|
||||
// Do the initial L10n setup, we do this before anything
|
||||
@ -574,6 +576,7 @@ loop.conversation = (function(mozL10n) {
|
||||
store: conversationStore,
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
dispatcher: dispatcher,
|
||||
sdk: window.OT}
|
||||
), document.querySelector('#main'));
|
||||
|
||||
|
@ -483,7 +483,8 @@ loop.conversation = (function(mozL10n) {
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
|
||||
// XXX New types for OutgoingConversationView
|
||||
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired
|
||||
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@ -505,6 +506,7 @@ loop.conversation = (function(mozL10n) {
|
||||
if (this.state.outgoing) {
|
||||
return (<OutgoingConversationView
|
||||
store={this.props.store}
|
||||
dispatcher={this.props.dispatcher}
|
||||
/>);
|
||||
}
|
||||
|
||||
@ -517,7 +519,7 @@ loop.conversation = (function(mozL10n) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Panel initialisation.
|
||||
* Conversation initialisation.
|
||||
*/
|
||||
function init() {
|
||||
// Do the initial L10n setup, we do this before anything
|
||||
@ -574,6 +576,7 @@ loop.conversation = (function(mozL10n) {
|
||||
store={conversationStore}
|
||||
client={client}
|
||||
conversation={conversation}
|
||||
dispatcher={dispatcher}
|
||||
sdk={window.OT}
|
||||
/>, document.querySelector('#main'));
|
||||
|
||||
|
@ -10,6 +10,9 @@ var loop = loop || {};
|
||||
loop.conversationViews = (function(mozL10n) {
|
||||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
/**
|
||||
* Displays details of the incoming/outgoing conversation
|
||||
@ -41,11 +44,24 @@ loop.conversationViews = (function(mozL10n) {
|
||||
*/
|
||||
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
callState: React.PropTypes.string,
|
||||
calleeId: React.PropTypes.string,
|
||||
enableCancelButton: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
enableCancelButton: false
|
||||
};
|
||||
},
|
||||
|
||||
cancelCall: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cx = React.addons.classSet;
|
||||
var pendingStateString;
|
||||
if (this.props.callState === CALL_STATES.ALERTING) {
|
||||
pendingStateString = mozL10n.get("call_progress_ringing_description");
|
||||
@ -53,6 +69,12 @@ loop.conversationViews = (function(mozL10n) {
|
||||
pendingStateString = mozL10n.get("call_progress_connecting_description");
|
||||
}
|
||||
|
||||
var btnCancelStyles = cx({
|
||||
"btn": true,
|
||||
"btn-cancel": true,
|
||||
"disabled": !this.props.enableCancelButton
|
||||
});
|
||||
|
||||
return (
|
||||
ConversationDetailView({calleeId: this.props.calleeId},
|
||||
|
||||
@ -60,7 +82,8 @@ loop.conversationViews = (function(mozL10n) {
|
||||
|
||||
React.DOM.div({className: "btn-group call-action-group"},
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||
React.DOM.button({className: "btn btn-cancel"},
|
||||
React.DOM.button({className: btnCancelStyles,
|
||||
onClick: this.cancelCall},
|
||||
mozL10n.get("initiate_call_cancel_button")
|
||||
),
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"})
|
||||
@ -75,10 +98,115 @@ loop.conversationViews = (function(mozL10n) {
|
||||
* Call failed view. Displayed when a call fails.
|
||||
*/
|
||||
var CallFailedView = React.createClass({displayName: 'CallFailedView',
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
retryCall: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RetryCall());
|
||||
},
|
||||
|
||||
cancelCall: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div({className: "call-window"},
|
||||
React.DOM.h2(null, mozL10n.get("generic_failure_title"))
|
||||
React.DOM.h2(null, mozL10n.get("generic_failure_title")),
|
||||
|
||||
React.DOM.p({className: "btn-label"}, mozL10n.get("generic_failure_no_reason2")),
|
||||
|
||||
React.DOM.div({className: "btn-group call-action-group"},
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||
React.DOM.button({className: "btn btn-accept btn-retry",
|
||||
onClick: this.retryCall},
|
||||
mozL10n.get("retry_call_button")
|
||||
),
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||
React.DOM.button({className: "btn btn-cancel",
|
||||
onClick: this.cancelCall},
|
||||
mozL10n.get("cancel_button")
|
||||
),
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var OngoingConversationView = React.createClass({displayName: 'OngoingConversationView',
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
video: React.PropTypes.object,
|
||||
audio: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
video: {enabled: true, visible: true},
|
||||
audio: {enabled: true, visible: true}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
/**
|
||||
* OT inserts inline styles into the markup. Using a listener for
|
||||
* resize events helps us trigger a full width/height on the element
|
||||
* so that they update to the correct dimensions.
|
||||
* XXX: this should be factored as a mixin.
|
||||
*/
|
||||
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||
window.addEventListener('resize', this.updateVideoContainer);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener('orientationchange', this.updateVideoContainer);
|
||||
window.removeEventListener('resize', this.updateVideoContainer);
|
||||
},
|
||||
|
||||
updateVideoContainer: function() {
|
||||
var localStreamParent = document.querySelector('.local .OT_publisher');
|
||||
var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
|
||||
if (localStreamParent) {
|
||||
localStreamParent.style.width = "100%";
|
||||
}
|
||||
if (remoteStreamParent) {
|
||||
remoteStreamParent.style.height = "100%";
|
||||
}
|
||||
},
|
||||
|
||||
hangup: function() {
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.HangupCall());
|
||||
},
|
||||
|
||||
publishStream: function(type, enabled) {
|
||||
// XXX Add this as part of bug 972017.
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var localStreamClasses = React.addons.classSet({
|
||||
local: true,
|
||||
"local-stream": true,
|
||||
"local-stream-audio": !this.props.video.enabled
|
||||
});
|
||||
|
||||
return (
|
||||
React.DOM.div({className: "video-layout-wrapper"},
|
||||
React.DOM.div({className: "conversation"},
|
||||
React.DOM.div({className: "media nested"},
|
||||
React.DOM.div({className: "video_wrapper remote_wrapper"},
|
||||
React.DOM.div({className: "video_inner remote"})
|
||||
),
|
||||
React.DOM.div({className: localStreamClasses})
|
||||
),
|
||||
loop.shared.views.ConversationToolbar({
|
||||
video: this.props.video,
|
||||
audio: this.props.audio,
|
||||
publishStream: this.publishStream,
|
||||
hangup: this.hangup})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -90,6 +218,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||
*/
|
||||
var OutgoingConversationView = React.createClass({displayName: 'OutgoingConversationView',
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
store: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationStore).isRequired
|
||||
},
|
||||
@ -104,22 +233,83 @@ loop.conversationViews = (function(mozL10n) {
|
||||
}, this);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.callState === CALL_STATES.TERMINATED) {
|
||||
return (CallFailedView(null));
|
||||
}
|
||||
_closeWindow: function() {
|
||||
window.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the call is in a cancellable state, during call setup.
|
||||
*/
|
||||
_isCancellable: function() {
|
||||
return this.state.callState !== CALL_STATES.INIT &&
|
||||
this.state.callState !== CALL_STATES.GATHER;
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to setup and render the feedback view.
|
||||
*/
|
||||
_renderFeedbackView: function() {
|
||||
document.title = mozL10n.get("conversation_has_ended");
|
||||
|
||||
// XXX Bug 1076754 Feedback view should be redone in the Flux style.
|
||||
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)}
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
switch (this.state.callState) {
|
||||
case CALL_STATES.CLOSE: {
|
||||
this._closeWindow();
|
||||
return null;
|
||||
}
|
||||
case CALL_STATES.TERMINATED: {
|
||||
return (CallFailedView({
|
||||
dispatcher: this.props.dispatcher}
|
||||
));
|
||||
}
|
||||
case CALL_STATES.ONGOING: {
|
||||
return (OngoingConversationView({
|
||||
dispatcher: this.props.dispatcher,
|
||||
video: {enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
|
||||
)
|
||||
);
|
||||
}
|
||||
case CALL_STATES.FINISHED: {
|
||||
return this._renderFeedbackView();
|
||||
}
|
||||
default: {
|
||||
return (PendingConversationView({
|
||||
dispatcher: this.props.dispatcher,
|
||||
callState: this.state.callState,
|
||||
calleeId: this.state.calleeId}
|
||||
calleeId: this.state.calleeId,
|
||||
enableCancelButton: this._isCancellable()}
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
PendingConversationView: PendingConversationView,
|
||||
ConversationDetailView: ConversationDetailView,
|
||||
CallFailedView: CallFailedView,
|
||||
OngoingConversationView: OngoingConversationView,
|
||||
OutgoingConversationView: OutgoingConversationView
|
||||
};
|
||||
|
||||
|
@ -10,6 +10,9 @@ var loop = loop || {};
|
||||
loop.conversationViews = (function(mozL10n) {
|
||||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
/**
|
||||
* Displays details of the incoming/outgoing conversation
|
||||
@ -41,11 +44,24 @@ loop.conversationViews = (function(mozL10n) {
|
||||
*/
|
||||
var PendingConversationView = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
callState: React.PropTypes.string,
|
||||
calleeId: React.PropTypes.string,
|
||||
enableCancelButton: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
enableCancelButton: false
|
||||
};
|
||||
},
|
||||
|
||||
cancelCall: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cx = React.addons.classSet;
|
||||
var pendingStateString;
|
||||
if (this.props.callState === CALL_STATES.ALERTING) {
|
||||
pendingStateString = mozL10n.get("call_progress_ringing_description");
|
||||
@ -53,6 +69,12 @@ loop.conversationViews = (function(mozL10n) {
|
||||
pendingStateString = mozL10n.get("call_progress_connecting_description");
|
||||
}
|
||||
|
||||
var btnCancelStyles = cx({
|
||||
"btn": true,
|
||||
"btn-cancel": true,
|
||||
"disabled": !this.props.enableCancelButton
|
||||
});
|
||||
|
||||
return (
|
||||
<ConversationDetailView calleeId={this.props.calleeId}>
|
||||
|
||||
@ -60,7 +82,8 @@ loop.conversationViews = (function(mozL10n) {
|
||||
|
||||
<div className="btn-group call-action-group">
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
<button className="btn btn-cancel">
|
||||
<button className={btnCancelStyles}
|
||||
onClick={this.cancelCall}>
|
||||
{mozL10n.get("initiate_call_cancel_button")}
|
||||
</button>
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
@ -75,10 +98,115 @@ loop.conversationViews = (function(mozL10n) {
|
||||
* Call failed view. Displayed when a call fails.
|
||||
*/
|
||||
var CallFailedView = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
retryCall: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RetryCall());
|
||||
},
|
||||
|
||||
cancelCall: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="call-window">
|
||||
<h2>{mozL10n.get("generic_failure_title")}</h2>
|
||||
|
||||
<p className="btn-label">{mozL10n.get("generic_failure_no_reason2")}</p>
|
||||
|
||||
<div className="btn-group call-action-group">
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
<button className="btn btn-accept btn-retry"
|
||||
onClick={this.retryCall}>
|
||||
{mozL10n.get("retry_call_button")}
|
||||
</button>
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
<button className="btn btn-cancel"
|
||||
onClick={this.cancelCall}>
|
||||
{mozL10n.get("cancel_button")}
|
||||
</button>
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var OngoingConversationView = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
video: React.PropTypes.object,
|
||||
audio: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
video: {enabled: true, visible: true},
|
||||
audio: {enabled: true, visible: true}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
/**
|
||||
* OT inserts inline styles into the markup. Using a listener for
|
||||
* resize events helps us trigger a full width/height on the element
|
||||
* so that they update to the correct dimensions.
|
||||
* XXX: this should be factored as a mixin.
|
||||
*/
|
||||
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||
window.addEventListener('resize', this.updateVideoContainer);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener('orientationchange', this.updateVideoContainer);
|
||||
window.removeEventListener('resize', this.updateVideoContainer);
|
||||
},
|
||||
|
||||
updateVideoContainer: function() {
|
||||
var localStreamParent = document.querySelector('.local .OT_publisher');
|
||||
var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
|
||||
if (localStreamParent) {
|
||||
localStreamParent.style.width = "100%";
|
||||
}
|
||||
if (remoteStreamParent) {
|
||||
remoteStreamParent.style.height = "100%";
|
||||
}
|
||||
},
|
||||
|
||||
hangup: function() {
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.HangupCall());
|
||||
},
|
||||
|
||||
publishStream: function(type, enabled) {
|
||||
// XXX Add this as part of bug 972017.
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var localStreamClasses = React.addons.classSet({
|
||||
local: true,
|
||||
"local-stream": true,
|
||||
"local-stream-audio": !this.props.video.enabled
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="video-layout-wrapper">
|
||||
<div className="conversation">
|
||||
<div className="media nested">
|
||||
<div className="video_wrapper remote_wrapper">
|
||||
<div className="video_inner remote"></div>
|
||||
</div>
|
||||
<div className={localStreamClasses}></div>
|
||||
</div>
|
||||
<loop.shared.views.ConversationToolbar
|
||||
video={this.props.video}
|
||||
audio={this.props.audio}
|
||||
publishStream={this.publishStream}
|
||||
hangup={this.hangup} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -90,6 +218,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||
*/
|
||||
var OutgoingConversationView = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
store: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationStore).isRequired
|
||||
},
|
||||
@ -104,22 +233,83 @@ loop.conversationViews = (function(mozL10n) {
|
||||
}, this);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.callState === CALL_STATES.TERMINATED) {
|
||||
return (<CallFailedView />);
|
||||
}
|
||||
_closeWindow: function() {
|
||||
window.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the call is in a cancellable state, during call setup.
|
||||
*/
|
||||
_isCancellable: function() {
|
||||
return this.state.callState !== CALL_STATES.INIT &&
|
||||
this.state.callState !== CALL_STATES.GATHER;
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to setup and render the feedback view.
|
||||
*/
|
||||
_renderFeedbackView: function() {
|
||||
document.title = mozL10n.get("conversation_has_ended");
|
||||
|
||||
// XXX Bug 1076754 Feedback view should be redone in the Flux style.
|
||||
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)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
switch (this.state.callState) {
|
||||
case CALL_STATES.CLOSE: {
|
||||
this._closeWindow();
|
||||
return null;
|
||||
}
|
||||
case CALL_STATES.TERMINATED: {
|
||||
return (<CallFailedView
|
||||
dispatcher={this.props.dispatcher}
|
||||
/>);
|
||||
}
|
||||
case CALL_STATES.ONGOING: {
|
||||
return (<OngoingConversationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
video={{enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case CALL_STATES.FINISHED: {
|
||||
return this._renderFeedbackView();
|
||||
}
|
||||
default: {
|
||||
return (<PendingConversationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
callState={this.state.callState}
|
||||
calleeId={this.state.calleeId}
|
||||
enableCancelButton={this._isCancellable()}
|
||||
/>)
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
PendingConversationView: PendingConversationView,
|
||||
ConversationDetailView: ConversationDetailView,
|
||||
CallFailedView: CallFailedView,
|
||||
OngoingConversationView: OngoingConversationView,
|
||||
OutgoingConversationView: OutgoingConversationView
|
||||
};
|
||||
|
||||
|
@ -47,6 +47,12 @@ loop.shared.actions = (function() {
|
||||
CancelCall: Action.define("cancelCall", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to retry a failed call.
|
||||
*/
|
||||
RetryCall: Action.define("retryCall", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to initiate connecting of a call with the relevant
|
||||
* sessionData.
|
||||
@ -57,14 +63,20 @@ loop.shared.actions = (function() {
|
||||
sessionData: Object
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used for hanging up the call at the end of a successful call.
|
||||
*/
|
||||
HangupCall: Action.define("hangupCall", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used for notifying of connection progress state changes.
|
||||
* The connection refers to the overall connection flow as indicated
|
||||
* on the websocket.
|
||||
*/
|
||||
ConnectionProgress: Action.define("connectionProgress", {
|
||||
// The new connection state
|
||||
state: String
|
||||
// The connection state from the websocket.
|
||||
wsState: String
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -10,19 +10,46 @@ loop.store = (function() {
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
|
||||
/**
|
||||
* Websocket states taken from:
|
||||
* https://docs.services.mozilla.com/loop/apis.html#call-progress-state-change-progress
|
||||
*/
|
||||
var WS_STATES = {
|
||||
// The call is starting, and the remote party is not yet being alerted.
|
||||
INIT: "init",
|
||||
// The called party is being alerted.
|
||||
ALERTING: "alerting",
|
||||
// The call is no longer being set up and has been aborted for some reason.
|
||||
TERMINATED: "terminated",
|
||||
// The called party has indicated that he has answered the call,
|
||||
// but the media is not yet confirmed.
|
||||
CONNECTING: "connecting",
|
||||
// One of the two parties has indicated successful media set up,
|
||||
// but the other has not yet.
|
||||
HALF_CONNECTED: "half-connected",
|
||||
// Both endpoints have reported successfully establishing media.
|
||||
CONNECTED: "connected"
|
||||
};
|
||||
|
||||
var CALL_STATES = {
|
||||
// The initial state of the view.
|
||||
INIT: "init",
|
||||
INIT: "cs-init",
|
||||
// The store is gathering the call data from the server.
|
||||
GATHER: "gather",
|
||||
// The websocket has connected to the server and is waiting
|
||||
// for the other peer to connect to the websocket.
|
||||
CONNECTING: "connecting",
|
||||
GATHER: "cs-gather",
|
||||
// The initial data has been gathered, the websocket is connecting, or has
|
||||
// connected, and waiting for the other side to connect to the server.
|
||||
CONNECTING: "cs-connecting",
|
||||
// The websocket has received information that we're now alerting
|
||||
// the peer.
|
||||
ALERTING: "alerting",
|
||||
ALERTING: "cs-alerting",
|
||||
// The call is ongoing.
|
||||
ONGOING: "cs-ongoing",
|
||||
// The call ended successfully.
|
||||
FINISHED: "cs-finished",
|
||||
// The user has finished with the window.
|
||||
CLOSE: "cs-close",
|
||||
// The call was terminated due to an issue during connection.
|
||||
TERMINATED: "terminated"
|
||||
TERMINATED: "cs-terminated"
|
||||
};
|
||||
|
||||
|
||||
@ -85,7 +112,10 @@ loop.store = (function() {
|
||||
"connectionFailure",
|
||||
"connectionProgress",
|
||||
"gatherCallData",
|
||||
"connectCall"
|
||||
"connectCall",
|
||||
"hangupCall",
|
||||
"cancelCall",
|
||||
"retryCall"
|
||||
]);
|
||||
},
|
||||
|
||||
@ -109,19 +139,29 @@ loop.store = (function() {
|
||||
* @param {sharedActions.ConnectionProgress} actionData The action data.
|
||||
*/
|
||||
connectionProgress: function(actionData) {
|
||||
// XXX Turn this into a state machine?
|
||||
if (actionData.state === "alerting" &&
|
||||
(this.get("callState") === CALL_STATES.CONNECTING ||
|
||||
this.get("callState") === CALL_STATES.GATHER)) {
|
||||
this.set({
|
||||
callState: CALL_STATES.ALERTING
|
||||
});
|
||||
var callState = this.get("callState");
|
||||
|
||||
switch(actionData.wsState) {
|
||||
case WS_STATES.INIT: {
|
||||
if (callState === CALL_STATES.GATHER) {
|
||||
this.set({callState: CALL_STATES.CONNECTING});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WS_STATES.ALERTING: {
|
||||
this.set({callState: CALL_STATES.ALERTING});
|
||||
break;
|
||||
}
|
||||
case WS_STATES.CONNECTING:
|
||||
case WS_STATES.HALF_CONNECTED:
|
||||
case WS_STATES.CONNECTED: {
|
||||
this.set({callState: CALL_STATES.ONGOING});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.error("Unexpected websocket state passed to connectionProgress:",
|
||||
actionData.wsState);
|
||||
}
|
||||
if (actionData.state === "connecting" &&
|
||||
this.get("callState") === CALL_STATES.GATHER) {
|
||||
this.set({
|
||||
callState: CALL_STATES.CONNECTING
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -156,6 +196,63 @@ loop.store = (function() {
|
||||
this._connectWebSocket();
|
||||
},
|
||||
|
||||
/**
|
||||
* Hangs up an ongoing call.
|
||||
*/
|
||||
hangupCall: function() {
|
||||
// XXX Stop the SDK once we add it.
|
||||
|
||||
// Ensure the websocket has been disconnected.
|
||||
if (this._websocket) {
|
||||
// Let the server know the user has hung up.
|
||||
this._websocket.mediaFail();
|
||||
this._ensureWebSocketDisconnected();
|
||||
}
|
||||
|
||||
this.set({callState: CALL_STATES.FINISHED});
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancels a call
|
||||
*/
|
||||
cancelCall: function() {
|
||||
var callState = this.get("callState");
|
||||
if (callState === CALL_STATES.TERMINATED) {
|
||||
// All we need to do is close the window.
|
||||
this.set({callState: CALL_STATES.CLOSE});
|
||||
return;
|
||||
}
|
||||
|
||||
if (callState === CALL_STATES.CONNECTING ||
|
||||
callState === CALL_STATES.ALERTING) {
|
||||
if (this._websocket) {
|
||||
// Let the server know the user has hung up.
|
||||
this._websocket.cancel();
|
||||
this._ensureWebSocketDisconnected();
|
||||
}
|
||||
this.set({callState: CALL_STATES.CLOSE});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Unsupported cancel in state", callState);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retries a call
|
||||
*/
|
||||
retryCall: function() {
|
||||
var callState = this.get("callState");
|
||||
if (callState !== CALL_STATES.TERMINATED) {
|
||||
console.error("Unexpected retry in state", callState);
|
||||
return;
|
||||
}
|
||||
|
||||
this.set({callState: CALL_STATES.GATHER});
|
||||
if (this.get("outgoing")) {
|
||||
this._setupOutgoingCall();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtains the outgoing call data from the server and handles the
|
||||
* result.
|
||||
@ -192,11 +289,11 @@ loop.store = (function() {
|
||||
});
|
||||
|
||||
this._websocket.promiseConnect().then(
|
||||
function() {
|
||||
function(progressState) {
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionProgress({
|
||||
// This is the websocket call state, i.e. waiting for the
|
||||
// other end to connect to the server.
|
||||
state: "connecting"
|
||||
wsState: progressState
|
||||
}));
|
||||
}.bind(this),
|
||||
function(error) {
|
||||
@ -207,7 +304,18 @@ loop.store = (function() {
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this._websocket.on("progress", this._handleWebSocketProgress, this);
|
||||
this.listenTo(this._websocket, "progress", this._handleWebSocketProgress);
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures the websocket gets disconnected.
|
||||
*/
|
||||
_ensureWebSocketDisconnected: function() {
|
||||
this.stopListening(this._websocket);
|
||||
|
||||
// Now close the websocket.
|
||||
this._websocket.close();
|
||||
delete this._websocket;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -218,19 +326,18 @@ loop.store = (function() {
|
||||
var action;
|
||||
|
||||
switch(progressData.state) {
|
||||
case "terminated":
|
||||
case WS_STATES.TERMINATED: {
|
||||
action = new sharedActions.ConnectionFailure({
|
||||
reason: progressData.reason
|
||||
});
|
||||
break;
|
||||
case "alerting":
|
||||
}
|
||||
default: {
|
||||
action = new sharedActions.ConnectionProgress({
|
||||
state: progressData.state
|
||||
wsState: progressData.state
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.warn("Received unexpected state in _handleWebSocketProgress", progressData.state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatcher.dispatch(action);
|
||||
@ -239,6 +346,7 @@ loop.store = (function() {
|
||||
|
||||
return {
|
||||
CALL_STATES: CALL_STATES,
|
||||
ConversationStore: ConversationStore
|
||||
ConversationStore: ConversationStore,
|
||||
WS_STATES: WS_STATES
|
||||
};
|
||||
})();
|
||||
|
@ -174,6 +174,17 @@ loop.CallConnectionWebSocket = (function() {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies the server that something failed during setup.
|
||||
*/
|
||||
mediaFail: function() {
|
||||
this._send({
|
||||
messageType: "action",
|
||||
event: "terminate",
|
||||
reason: "media-fail"
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends data on the websocket.
|
||||
*
|
||||
|
@ -4,7 +4,7 @@
|
||||
var expect = chai.expect;
|
||||
|
||||
describe("loop.conversationViews", function () {
|
||||
var sandbox, oldTitle, view;
|
||||
var sandbox, oldTitle, view, dispatcher;
|
||||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
|
||||
@ -15,6 +15,9 @@ describe("loop.conversationViews", function () {
|
||||
sandbox.stub(document.mozL10n, "get", function(x) {
|
||||
return x;
|
||||
});
|
||||
|
||||
dispatcher = new loop.Dispatcher();
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@ -53,7 +56,8 @@ describe("loop.conversationViews", function () {
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
callState: CALL_STATES.CONNECTING,
|
||||
calleeId: "mrsmith"
|
||||
calleeId: "mrsmith",
|
||||
dispatcher: dispatcher
|
||||
});
|
||||
|
||||
var label = TestUtils.findRenderedDOMComponentWithClass(
|
||||
@ -66,7 +70,8 @@ describe("loop.conversationViews", function () {
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
callState: CALL_STATES.ALERTING,
|
||||
calleeId: "mrsmith"
|
||||
calleeId: "mrsmith",
|
||||
dispatcher: dispatcher
|
||||
});
|
||||
|
||||
var label = TestUtils.findRenderedDOMComponentWithClass(
|
||||
@ -74,6 +79,86 @@ describe("loop.conversationViews", function () {
|
||||
|
||||
expect(label).to.have.string("ringing");
|
||||
});
|
||||
|
||||
it("should disable the cancel button if enableCancelButton is false",
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
callState: CALL_STATES.CONNECTING,
|
||||
calleeId: "mrsmith",
|
||||
dispatcher: dispatcher,
|
||||
enableCancelButton: false
|
||||
});
|
||||
|
||||
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
|
||||
|
||||
expect(cancelBtn.classList.contains("disabled")).eql(true);
|
||||
});
|
||||
|
||||
it("should enable the cancel button if enableCancelButton is false",
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
callState: CALL_STATES.CONNECTING,
|
||||
calleeId: "mrsmith",
|
||||
dispatcher: dispatcher,
|
||||
enableCancelButton: true
|
||||
});
|
||||
|
||||
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
|
||||
|
||||
expect(cancelBtn.classList.contains("disabled")).eql(false);
|
||||
});
|
||||
|
||||
it("should dispatch a cancelCall action when the cancel button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
callState: CALL_STATES.CONNECTING,
|
||||
calleeId: "mrsmith",
|
||||
dispatcher: dispatcher
|
||||
});
|
||||
|
||||
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(cancelBtn);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "cancelCall"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("CallFailedView", function() {
|
||||
function mountTestComponent(props) {
|
||||
return TestUtils.renderIntoDocument(
|
||||
loop.conversationViews.CallFailedView({
|
||||
dispatcher: dispatcher
|
||||
}));
|
||||
}
|
||||
|
||||
it("should dispatch a retryCall action when the retry button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
var retryBtn = view.getDOMNode().querySelector('.btn-retry');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(retryBtn);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "retryCall"));
|
||||
});
|
||||
|
||||
it("should dispatch a cancelCall action when the cancel button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(cancelBtn);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "cancelCall"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("OutgoingConversationView", function() {
|
||||
@ -88,9 +173,18 @@ describe("loop.conversationViews", function () {
|
||||
|
||||
beforeEach(function() {
|
||||
store = new loop.store.ConversationStore({}, {
|
||||
dispatcher: new loop.Dispatcher(),
|
||||
dispatcher: dispatcher,
|
||||
client: {}
|
||||
});
|
||||
|
||||
navigator.mozLoop = {
|
||||
getLoopCharPref: function() { return "fake"; },
|
||||
appVersionInfo: sinon.spy()
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
delete navigator.mozLoop;
|
||||
});
|
||||
|
||||
it("should render the CallFailedView when the call state is 'terminated'",
|
||||
@ -103,9 +197,9 @@ describe("loop.conversationViews", function () {
|
||||
loop.conversationViews.CallFailedView);
|
||||
});
|
||||
|
||||
it("should render the PendingConversationView when the call state is connecting",
|
||||
it("should render the PendingConversationView when the call state is 'init'",
|
||||
function() {
|
||||
store.set({callState: CALL_STATES.CONNECTING});
|
||||
store.set({callState: CALL_STATES.INIT});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
@ -113,9 +207,29 @@ describe("loop.conversationViews", function () {
|
||||
loop.conversationViews.PendingConversationView);
|
||||
});
|
||||
|
||||
it("should render the OngoingConversationView when the call state is 'ongoing'",
|
||||
function() {
|
||||
store.set({callState: CALL_STATES.ONGOING});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.conversationViews.OngoingConversationView);
|
||||
});
|
||||
|
||||
it("should render the FeedbackView when the call state is 'finished'",
|
||||
function() {
|
||||
store.set({callState: CALL_STATES.FINISHED});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.shared.views.FeedbackView);
|
||||
});
|
||||
|
||||
it("should update the rendered views when the state is changed.",
|
||||
function() {
|
||||
store.set({callState: CALL_STATES.CONNECTING});
|
||||
store.set({callState: CALL_STATES.INIT});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
|
@ -7,6 +7,7 @@ describe("loop.ConversationStore", function () {
|
||||
"use strict";
|
||||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var WS_STATES = loop.store.WS_STATES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var sandbox, dispatcher, client, store, fakeSessionData;
|
||||
@ -86,36 +87,69 @@ describe("loop.ConversationStore", function () {
|
||||
});
|
||||
|
||||
describe("#connectionProgress", function() {
|
||||
describe("progress: connecting", function() {
|
||||
describe("progress: init", function() {
|
||||
it("should change the state from 'gather' to 'connecting'", function() {
|
||||
store.set({callState: CALL_STATES.GATHER});
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({state: "connecting"}));
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.INIT}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.CONNECTING);
|
||||
});
|
||||
});
|
||||
|
||||
describe("progress: alerting", function() {
|
||||
it("should set the state from 'gather' to 'alerting'", function() {
|
||||
it("should change the state from 'gather' to 'alerting'", function() {
|
||||
store.set({callState: CALL_STATES.GATHER});
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({state: "alerting"}));
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ALERTING);
|
||||
});
|
||||
|
||||
it("should set the state from 'connecting' to 'alerting'", function() {
|
||||
store.set({callState: CALL_STATES.CONNECTING});
|
||||
it("should change the state from 'init' to 'alerting'", function() {
|
||||
store.set({callState: CALL_STATES.INIT});
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({state: "alerting"}));
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ALERTING);
|
||||
});
|
||||
});
|
||||
|
||||
describe("progress: connecting", function() {
|
||||
it("should change the state to 'ongoing'", function() {
|
||||
store.set({callState: CALL_STATES.ALERTING});
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||
});
|
||||
});
|
||||
|
||||
describe("progress: half-connected", function() {
|
||||
it("should change the state to 'ongoing'", function() {
|
||||
store.set({callState: CALL_STATES.ALERTING});
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.HALF_CONNECTED}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||
});
|
||||
});
|
||||
|
||||
describe("progress: connecting", function() {
|
||||
it("should change the state to 'ongoing'", function() {
|
||||
store.set({callState: CALL_STATES.ALERTING});
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTED}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#gatherCallData", function() {
|
||||
@ -250,7 +284,7 @@ describe("loop.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should dispatch a connection progress action on success", function(done) {
|
||||
resolveConnectPromise();
|
||||
resolveConnectPromise(WS_STATES.INIT);
|
||||
|
||||
connectPromise.then(function() {
|
||||
checkFailures(done, function() {
|
||||
@ -259,7 +293,7 @@ describe("loop.ConversationStore", function () {
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "connectionProgress"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("state", "connecting"));
|
||||
sinon.match.hasOwn("wsState", WS_STATES.INIT));
|
||||
});
|
||||
}, function() {
|
||||
done(new Error("Promise should have been resolve, not rejected"));
|
||||
@ -282,7 +316,105 @@ describe("loop.ConversationStore", function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#hangupCall", function() {
|
||||
var wsMediaFailSpy, wsCloseSpy;
|
||||
beforeEach(function() {
|
||||
wsMediaFailSpy = sinon.spy();
|
||||
wsCloseSpy = sinon.spy();
|
||||
|
||||
store._websocket = {
|
||||
mediaFail: wsMediaFailSpy,
|
||||
close: wsCloseSpy
|
||||
};
|
||||
store.set({callState: CALL_STATES.ONGOING});
|
||||
});
|
||||
|
||||
it("should send a media-fail message to the websocket if it is open", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
|
||||
sinon.assert.calledOnce(wsMediaFailSpy);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the callState to finished", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#cancelCall", function() {
|
||||
it("should set the state to close if the call has terminated already", function() {
|
||||
store.set({callState: CALL_STATES.TERMINATED});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||
});
|
||||
|
||||
describe("whilst connecting", function() {
|
||||
var wsCancelSpy, wsCloseSpy;
|
||||
beforeEach(function() {
|
||||
wsCancelSpy = sinon.spy();
|
||||
wsCloseSpy = sinon.spy();
|
||||
|
||||
store._websocket = {
|
||||
cancel: wsCancelSpy,
|
||||
close: wsCloseSpy
|
||||
};
|
||||
store.set({callState: CALL_STATES.CONNECTING});
|
||||
});
|
||||
|
||||
it("should send a cancel message to the websocket if it is open", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCancelSpy);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the state to close if the call is connecting", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#retryCall", function() {
|
||||
it("should set the state to gather", function() {
|
||||
store.set({callState: CALL_STATES.TERMINATED});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.RetryCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.GATHER);
|
||||
});
|
||||
|
||||
it("should request the outgoing call data", function() {
|
||||
store.set({
|
||||
callState: CALL_STATES.TERMINATED,
|
||||
outgoing: true,
|
||||
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO,
|
||||
calleeId: "fake"
|
||||
});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.RetryCall());
|
||||
|
||||
sinon.assert.calledOnce(client.setupOutgoingCall);
|
||||
sinon.assert.calledWith(client.setupOutgoingCall,
|
||||
["fake"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
||||
});
|
||||
});
|
||||
|
||||
@ -296,7 +428,10 @@ describe("loop.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should dispatch a connection failure action on 'terminate'", function() {
|
||||
store._websocket.trigger("progress", {state: "terminated", reason: "reject"});
|
||||
store._websocket.trigger("progress", {
|
||||
state: WS_STATES.TERMINATED,
|
||||
reason: "reject"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
// Can't use instanceof here, as that matches any action
|
||||
@ -307,14 +442,14 @@ describe("loop.ConversationStore", function () {
|
||||
});
|
||||
|
||||
it("should dispatch a connection progress action on 'alerting'", function() {
|
||||
store._websocket.trigger("progress", {state: "alerting"});
|
||||
store._websocket.trigger("progress", {state: WS_STATES.ALERTING});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
// Can't use instanceof here, as that matches any action
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "connectionProgress"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("state", "alerting"));
|
||||
sinon.match.hasOwn("wsState", WS_STATES.ALERTING));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -206,6 +206,22 @@ describe("loop.CallConnectionWebSocket", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#mediaFail", function() {
|
||||
it("should send a terminate message to the server with a reason of media-fail",
|
||||
function() {
|
||||
callWebSocket.promiseConnect();
|
||||
|
||||
callWebSocket.mediaFail();
|
||||
|
||||
sinon.assert.calledOnce(dummySocket.send);
|
||||
sinon.assert.calledWithExactly(dummySocket.send, JSON.stringify({
|
||||
messageType: "action",
|
||||
event: "terminate",
|
||||
reason: "media-fail"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(callWebSocket, "trigger");
|
||||
|
@ -10,12 +10,17 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
// Stop the default init functions running to avoid conflicts.
|
||||
document.removeEventListener('DOMContentLoaded', loop.panel.init);
|
||||
document.removeEventListener('DOMContentLoaded', loop.conversation.init);
|
||||
|
||||
// 1. Desktop components
|
||||
// 1.1 Panel
|
||||
var PanelView = loop.panel.PanelView;
|
||||
// 1.2. Conversation Window
|
||||
var IncomingCallView = loop.conversation.IncomingCallView;
|
||||
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
||||
var CallFailedView = loop.conversationViews.CallFailedView;
|
||||
|
||||
// 2. Standalone webapp
|
||||
var HomeView = loop.webapp.HomeView;
|
||||
@ -249,6 +254,15 @@
|
||||
)
|
||||
),
|
||||
|
||||
Section({name: "CallFailedView"},
|
||||
Example({summary: "Call Failed", dashed: "true",
|
||||
style: {width: "260px", height: "265px"}},
|
||||
React.DOM.div({className: "fx-embedded"},
|
||||
CallFailedView(null)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
Section({name: "StartConversationView"},
|
||||
Example({summary: "Start conversation view", dashed: "true"},
|
||||
React.DOM.div({className: "standalone"},
|
||||
|
@ -10,12 +10,17 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
// Stop the default init functions running to avoid conflicts.
|
||||
document.removeEventListener('DOMContentLoaded', loop.panel.init);
|
||||
document.removeEventListener('DOMContentLoaded', loop.conversation.init);
|
||||
|
||||
// 1. Desktop components
|
||||
// 1.1 Panel
|
||||
var PanelView = loop.panel.PanelView;
|
||||
// 1.2. Conversation Window
|
||||
var IncomingCallView = loop.conversation.IncomingCallView;
|
||||
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
||||
var CallFailedView = loop.conversationViews.CallFailedView;
|
||||
|
||||
// 2. Standalone webapp
|
||||
var HomeView = loop.webapp.HomeView;
|
||||
@ -249,6 +254,15 @@
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="CallFailedView">
|
||||
<Example summary="Call Failed" dashed="true"
|
||||
style={{width: "260px", height: "265px"}}>
|
||||
<div className="fx-embedded">
|
||||
<CallFailedView />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="StartConversationView">
|
||||
<Example summary="Start conversation view" dashed="true">
|
||||
<div className="standalone">
|
||||
|
Loading…
Reference in New Issue
Block a user