Bug 1072323: Hook up the contact menus to be able to start outgoing calls. r=mikedeboer

This commit is contained in:
Mark Banner 2014-10-07 17:10:56 +02:00
parent 21e19674e9
commit 86712e23bc
16 changed files with 377 additions and 92 deletions

View File

@ -592,11 +592,26 @@ function injectLoopAPI(targetWindow) {
return MozLoopService.generateUUID();
}
},
/**
* Starts a direct call to the contact addresses.
*
* @param {Object} contact The contact to call
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
* @return true if the call is opened, false if it is not opened (i.e. busy)
*/
startDirectCall: {
enumerable: true,
writable: true,
value: function(contact, callType) {
MozLoopService.startDirectCall(contact, callType);
}
},
};
function onStatusChanged(aSubject, aTopic, aData) {
let event = new targetWindow.CustomEvent("LoopStatusChanged");
targetWindow.dispatchEvent(event)
targetWindow.dispatchEvent(event);
};
function onDOMWindowDestroyed(aSubject, aTopic, aData) {

View File

@ -719,13 +719,8 @@ let MozLoopServiceInternal = {
if (respData.calls && Array.isArray(respData.calls)) {
respData.calls.forEach((callData) => {
if (!this.callsData.inUse) {
this.callsData.inUse = true;
callData.sessionType = sessionType;
this.callsData.data = callData;
this.openChatWindow(
null,
this.localizedStrings["incoming_call_title2"].textContent,
"about:loopconversation#incoming/" + callData.callId);
this._startCall(callData, "incoming");
} else {
this._returnBusy(callData);
}
@ -738,6 +733,44 @@ let MozLoopServiceInternal = {
}
},
/**
* Starts a call, saves the call data, and opens a chat window.
*
* @param {Object} callData The data associated with the call including an id.
* @param {Boolean} conversationType Whether or not the call is "incoming"
* or "outgoing"
*/
_startCall: function(callData, conversationType) {
this.callsData.inUse = true;
this.callsData.data = callData;
this.openChatWindow(
null,
// No title, let the page set that, to avoid flickering.
"",
"about:loopconversation#" + conversationType + "/" + callData.callId);
},
/**
* Starts a direct call to the contact addresses.
*
* @param {Object} contact The contact to call
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
* @return true if the call is opened, false if it is not opened (i.e. busy)
*/
startDirectCall: function(contact, callType) {
if (this.callsData.inUse)
return false;
var callData = {
contact: contact,
callType: callType,
callId: Math.floor((Math.random() * 10))
};
this._startCall(callData, "outgoing");
return true;
},
/**
* Open call progress websocket and terminate with a reason of busy
* the server.
@ -1505,4 +1538,15 @@ this.MozLoopService = {
return MozLoopServiceInternal.hawkRequest(sessionType, path, method, payloadObj).catch(
error => {MozLoopServiceInternal._hawkRequestError(error);});
},
/**
* Starts a direct call to the contact addresses.
*
* @param {Object} contact The contact to call
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
* @return true if the call is opened, false if it is not opened (i.e. busy)
*/
startDirectCall: function(contact, callType) {
MozLoopServiceInternal.startDirectCall(contact, callType);
},
};

View File

@ -13,6 +13,7 @@ loop.contacts = (function(_, mozL10n) {
const Button = loop.shared.views.Button;
const ButtonGroup = loop.shared.views.ButtonGroup;
const CALL_TYPES = loop.shared.utils.CALL_TYPES;
// Number of contacts to add to the list at the same time.
const CONTACTS_CHUNK_SIZE = 100;
@ -85,14 +86,12 @@ loop.contacts = (function(_, mozL10n) {
return (
React.DOM.ul({className: cx({ "dropdown-menu": true,
"dropdown-menu-up": this.state.openDirUp })},
React.DOM.li({className: cx({ "dropdown-menu-item": true,
"disabled": true }),
React.DOM.li({className: cx({ "dropdown-menu-item": true }),
onClick: this.onItemClick, 'data-action': "video-call"},
React.DOM.i({className: "icon icon-video-call"}),
mozL10n.get("video_call_menu_button")
),
React.DOM.li({className: cx({ "dropdown-menu-item": true,
"disabled": true }),
React.DOM.li({className: cx({ "dropdown-menu-item": true }),
onClick: this.onItemClick, 'data-action': "audio-call"},
React.DOM.i({className: "icon icon-audio-call"}),
mozL10n.get("audio_call_menu_button")
@ -160,7 +159,8 @@ loop.contacts = (function(_, mozL10n) {
return (
currContact.name[0] !== nextContact.name[0] ||
currContact.blocked !== nextContact.blocked ||
getPreferredEmail(currContact).value !== getPreferredEmail(nextContact).value
getPreferredEmail(currContact).value !==
getPreferredEmail(nextContact).value
);
},
@ -316,6 +316,12 @@ loop.contacts = (function(_, mozL10n) {
}
});
break;
case "video-call":
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
break;
case "audio-call":
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
break;
default:
console.error("Unrecognized action: " + actionName);
break;

View File

@ -13,6 +13,7 @@ loop.contacts = (function(_, mozL10n) {
const Button = loop.shared.views.Button;
const ButtonGroup = loop.shared.views.ButtonGroup;
const CALL_TYPES = loop.shared.utils.CALL_TYPES;
// Number of contacts to add to the list at the same time.
const CONTACTS_CHUNK_SIZE = 100;
@ -85,14 +86,12 @@ loop.contacts = (function(_, mozL10n) {
return (
<ul className={cx({ "dropdown-menu": true,
"dropdown-menu-up": this.state.openDirUp })}>
<li className={cx({ "dropdown-menu-item": true,
"disabled": true })}
<li className={cx({ "dropdown-menu-item": true })}
onClick={this.onItemClick} data-action="video-call">
<i className="icon icon-video-call" />
{mozL10n.get("video_call_menu_button")}
</li>
<li className={cx({ "dropdown-menu-item": true,
"disabled": true })}
<li className={cx({ "dropdown-menu-item": true })}
onClick={this.onItemClick} data-action="audio-call">
<i className="icon icon-audio-call" />
{mozL10n.get("audio_call_menu_button")}
@ -160,7 +159,8 @@ loop.contacts = (function(_, mozL10n) {
return (
currContact.name[0] !== nextContact.name[0] ||
currContact.blocked !== nextContact.blocked ||
getPreferredEmail(currContact).value !== getPreferredEmail(nextContact).value
getPreferredEmail(currContact).value !==
getPreferredEmail(nextContact).value
);
},
@ -316,6 +316,12 @@ loop.contacts = (function(_, mozL10n) {
}
});
break;
case "video-call":
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
break;
case "audio-call":
navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
break;
default:
console.error("Unrecognized action: " + actionName);
break;

View File

@ -551,10 +551,6 @@ loop.conversation = (function(mozL10n) {
sdkDriver: sdkDriver
});
// XXX For now key this on the pref, but this should really be
// set by the information from the mozLoop API when we can get it (bug 1072323).
var outgoingEmail = navigator.mozLoop.getLoopCharPref("outgoingemail");
// XXX Old class creation for the incoming conversation view, whilst
// we transition across (bug 1072323).
var conversation = new sharedModels.ConversationModel(
@ -566,14 +562,25 @@ loop.conversation = (function(mozL10n) {
var helper = new loop.shared.utils.Helper();
var locationHash = helper.locationHash();
var callId;
if (locationHash) {
callId = locationHash.match(/\#incoming\/(.*)/)[1]
conversation.set("callId", callId);
var outgoing;
var hash = locationHash.match(/\#incoming\/(.*)/);
if (hash) {
callId = hash[1];
outgoing = false;
} else {
hash = locationHash.match(/\#outgoing\/(.*)/);
if (hash) {
callId = hash[1];
outgoing = true;
}
}
conversation.set({callId: callId});
window.addEventListener("unload", function(event) {
// Handle direct close of dialog box via [x] control.
navigator.mozLoop.releaseCallData(conversation.get("callId"));
navigator.mozLoop.releaseCallData(callId);
});
document.body.classList.add(loop.shared.utils.getTargetPlatform());
@ -588,7 +595,7 @@ loop.conversation = (function(mozL10n) {
dispatcher.dispatch(new loop.shared.actions.GatherCallData({
callId: callId,
calleeId: outgoingEmail
outgoing: outgoing
}));
}

View File

@ -551,10 +551,6 @@ loop.conversation = (function(mozL10n) {
sdkDriver: sdkDriver
});
// XXX For now key this on the pref, but this should really be
// set by the information from the mozLoop API when we can get it (bug 1072323).
var outgoingEmail = navigator.mozLoop.getLoopCharPref("outgoingemail");
// XXX Old class creation for the incoming conversation view, whilst
// we transition across (bug 1072323).
var conversation = new sharedModels.ConversationModel(
@ -566,14 +562,25 @@ loop.conversation = (function(mozL10n) {
var helper = new loop.shared.utils.Helper();
var locationHash = helper.locationHash();
var callId;
if (locationHash) {
callId = locationHash.match(/\#incoming\/(.*)/)[1]
conversation.set("callId", callId);
var outgoing;
var hash = locationHash.match(/\#incoming\/(.*)/);
if (hash) {
callId = hash[1];
outgoing = false;
} else {
hash = locationHash.match(/\#outgoing\/(.*)/);
if (hash) {
callId = hash[1];
outgoing = true;
}
}
conversation.set({callId: callId});
window.addEventListener("unload", function(event) {
// Handle direct close of dialog box via [x] control.
navigator.mozLoop.releaseCallData(conversation.get("callId"));
navigator.mozLoop.releaseCallData(callId);
});
document.body.classList.add(loop.shared.utils.getTargetPlatform());
@ -588,7 +595,7 @@ loop.conversation = (function(mozL10n) {
dispatcher.dispatch(new loop.shared.actions.GatherCallData({
callId: callId,
calleeId: outgoingEmail
outgoing: outgoing
}));
}

View File

@ -23,15 +23,35 @@ loop.conversationViews = (function(mozL10n) {
*/
var ConversationDetailView = React.createClass({displayName: 'ConversationDetailView',
propTypes: {
calleeId: React.PropTypes.string,
contact: React.PropTypes.object
},
// This duplicates a similar function in contacts.jsx that isn't used in the
// conversation window. If we get too many of these, we might want to consider
// finding a logical place for them to be shared.
_getPreferredEmail: function(contact) {
// A contact may not contain email addresses, but only a phone number.
if (!contact.email || contact.email.length == 0) {
return { value: "" };
}
return contact.email.find(e => e.pref) || contact.email[0];
},
render: function() {
document.title = this.props.calleeId;
var contactName;
if (this.props.contact.name &&
this.props.contact.name[0]) {
contactName = this.props.contact.name[0];
} else {
contactName = this._getPreferredEmail(this.props.contact).value;
}
document.title = contactName;
return (
React.DOM.div({className: "call-window"},
React.DOM.h2(null, this.props.calleeId),
React.DOM.h2(null, contactName),
React.DOM.div(null, this.props.children)
)
);
@ -46,7 +66,7 @@ loop.conversationViews = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
callState: React.PropTypes.string,
calleeId: React.PropTypes.string,
contact: React.PropTypes.object,
enableCancelButton: React.PropTypes.bool
},
@ -76,7 +96,7 @@ loop.conversationViews = (function(mozL10n) {
});
return (
ConversationDetailView({calleeId: this.props.calleeId},
ConversationDetailView({contact: this.props.contact},
React.DOM.p({className: "btn-label"}, pendingStateString),
@ -340,8 +360,8 @@ loop.conversationViews = (function(mozL10n) {
case CALL_STATES.ONGOING: {
return (OngoingConversationView({
dispatcher: this.props.dispatcher,
video: {enabled: this.state.videoMuted},
audio: {enabled: this.state.audioMuted}}
video: {enabled: !this.state.videoMuted},
audio: {enabled: !this.state.audioMuted}}
)
);
}
@ -352,7 +372,7 @@ loop.conversationViews = (function(mozL10n) {
return (PendingConversationView({
dispatcher: this.props.dispatcher,
callState: this.state.callState,
calleeId: this.state.calleeId,
contact: this.state.contact,
enableCancelButton: this._isCancellable()}
))
}

View File

@ -23,15 +23,35 @@ loop.conversationViews = (function(mozL10n) {
*/
var ConversationDetailView = React.createClass({
propTypes: {
calleeId: React.PropTypes.string,
contact: React.PropTypes.object
},
// This duplicates a similar function in contacts.jsx that isn't used in the
// conversation window. If we get too many of these, we might want to consider
// finding a logical place for them to be shared.
_getPreferredEmail: function(contact) {
// A contact may not contain email addresses, but only a phone number.
if (!contact.email || contact.email.length == 0) {
return { value: "" };
}
return contact.email.find(e => e.pref) || contact.email[0];
},
render: function() {
document.title = this.props.calleeId;
var contactName;
if (this.props.contact.name &&
this.props.contact.name[0]) {
contactName = this.props.contact.name[0];
} else {
contactName = this._getPreferredEmail(this.props.contact).value;
}
document.title = contactName;
return (
<div className="call-window">
<h2>{this.props.calleeId}</h2>
<h2>{contactName}</h2>
<div>{this.props.children}</div>
</div>
);
@ -46,7 +66,7 @@ loop.conversationViews = (function(mozL10n) {
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
callState: React.PropTypes.string,
calleeId: React.PropTypes.string,
contact: React.PropTypes.object,
enableCancelButton: React.PropTypes.bool
},
@ -76,7 +96,7 @@ loop.conversationViews = (function(mozL10n) {
});
return (
<ConversationDetailView calleeId={this.props.calleeId}>
<ConversationDetailView contact={this.props.contact}>
<p className="btn-label">{pendingStateString}</p>
@ -340,8 +360,8 @@ loop.conversationViews = (function(mozL10n) {
case CALL_STATES.ONGOING: {
return (<OngoingConversationView
dispatcher={this.props.dispatcher}
video={{enabled: this.state.videoMuted}}
audio={{enabled: this.state.audioMuted}}
video={{enabled: !this.state.videoMuted}}
audio={{enabled: !this.state.audioMuted}}
/>
);
}
@ -352,7 +372,7 @@ loop.conversationViews = (function(mozL10n) {
return (<PendingConversationView
dispatcher={this.props.dispatcher}
callState={this.state.callState}
calleeId={this.state.calleeId}
contact={this.state.contact}
enableCancelButton={this._isCancellable()}
/>)
}

View File

@ -34,11 +34,9 @@ loop.shared.actions = (function() {
* Used to trigger gathering of initial call data.
*/
GatherCallData: Action.define("gatherCallData", {
// XXX This may change when bug 1072323 is implemented.
// Optional: Specify the calleeId for an outgoing call
calleeId: [String, null],
// Specify the callId for an incoming call.
callId: [String, null]
callId: [String, null],
outgoing: Boolean
}),
/**

View File

@ -63,8 +63,8 @@ loop.store = (function() {
error: undefined,
// True if the call is outgoing, false if not, undefined if unknown
outgoing: undefined,
// The id of the person being called for outgoing calls
calleeId: undefined,
// The contact being called for outgoing calls
contact: undefined,
// The call type for the call.
// XXX Don't hard-code, this comes from the data in bug 1072323
callType: CALL_TYPES.AUDIO_VIDEO,
@ -83,9 +83,9 @@ loop.store = (function() {
// SDK session token
sessionToken: undefined,
// If the audio is muted
audioMuted: true,
audioMuted: false,
// If the video is muted
videoMuted: true
videoMuted: false
},
/**
@ -192,14 +192,29 @@ loop.store = (function() {
* @param {sharedActions.GatherCallData} actionData The action data.
*/
gatherCallData: function(actionData) {
if (!actionData.outgoing) {
// XXX Other types aren't supported yet, but set the state for the
// view selection.
this.set({outgoing: false});
return;
}
var callData = navigator.mozLoop.getCallData(actionData.callId);
if (!callData) {
console.error("Failed to get the call data");
this.set({callState: CALL_STATES.TERMINATED});
return;
}
this.set({
calleeId: actionData.calleeId,
outgoing: !!actionData.calleeId,
contact: callData.contact,
outgoing: actionData.outgoing,
callId: actionData.callId,
callType: callData.callType,
callState: CALL_STATES.GATHER
});
this.videoMuted = this.get("callType") !== CALL_TYPES.AUDIO_VIDEO;
this.set({videoMuted: this.get("callType") === CALL_TYPES.AUDIO_ONLY});
if (this.get("outgoing")) {
this._setupOutgoingCall();
@ -285,7 +300,7 @@ loop.store = (function() {
*/
setMute: function(actionData) {
var muteType = actionData.type + "Muted";
this.set(muteType, actionData.enabled);
this.set(muteType, !actionData.enabled);
},
/**
@ -293,8 +308,13 @@ loop.store = (function() {
* result.
*/
_setupOutgoingCall: function() {
// XXX For now, we only have one calleeId, so just wrap that in an array.
this.client.setupOutgoingCall([this.get("calleeId")],
var contactAddresses = [];
this.get("contact").email.forEach(function(address) {
contactAddresses.push(address.value);
});
this.client.setupOutgoingCall(contactAddresses,
this.get("callType"),
function(err, result) {
if (err) {

View File

@ -4,7 +4,7 @@
var expect = chai.expect;
describe("loop.conversationViews", function () {
var sandbox, oldTitle, view, dispatcher;
var sandbox, oldTitle, view, dispatcher, contact;
var CALL_STATES = loop.store.CALL_STATES;
@ -18,6 +18,15 @@ describe("loop.conversationViews", function () {
dispatcher = new loop.Dispatcher();
sandbox.stub(dispatcher, "dispatch");
contact = {
name: [ "mrsmith" ],
email: [{
type: "home",
value: "fakeEmail",
pref: true
}]
};
});
afterEach(function() {
@ -33,17 +42,28 @@ describe("loop.conversationViews", function () {
}
it("should set the document title to the calledId", function() {
mountTestComponent({calleeId: "mrsmith"});
mountTestComponent({contact: contact});
expect(document.title).eql("mrsmith");
});
it("should set display the calledId", function() {
view = mountTestComponent({calleeId: "mrsmith"});
view = mountTestComponent({contact: contact});
expect(TestUtils.findRenderedDOMComponentWithTag(
view, "h2").props.children).eql("mrsmith");
});
it("should fallback to the email if the contact name is not defined",
function() {
delete contact.name;
view = mountTestComponent({contact: contact});
expect(TestUtils.findRenderedDOMComponentWithTag(
view, "h2").props.children).eql("fakeEmail");
}
);
});
describe("PendingConversationView", function() {
@ -56,7 +76,7 @@ describe("loop.conversationViews", function () {
function() {
view = mountTestComponent({
callState: CALL_STATES.CONNECTING,
calleeId: "mrsmith",
contact: contact,
dispatcher: dispatcher
});
@ -70,7 +90,7 @@ describe("loop.conversationViews", function () {
function() {
view = mountTestComponent({
callState: CALL_STATES.ALERTING,
calleeId: "mrsmith",
contact: contact,
dispatcher: dispatcher
});
@ -84,7 +104,7 @@ describe("loop.conversationViews", function () {
function() {
view = mountTestComponent({
callState: CALL_STATES.CONNECTING,
calleeId: "mrsmith",
contact: contact,
dispatcher: dispatcher,
enableCancelButton: false
});
@ -98,7 +118,7 @@ describe("loop.conversationViews", function () {
function() {
view = mountTestComponent({
callState: CALL_STATES.CONNECTING,
calleeId: "mrsmith",
contact: contact,
dispatcher: dispatcher,
enableCancelButton: true
});
@ -112,7 +132,7 @@ describe("loop.conversationViews", function () {
function() {
view = mountTestComponent({
callState: CALL_STATES.CONNECTING,
calleeId: "mrsmith",
contact: contact,
dispatcher: dispatcher
});
@ -293,7 +313,10 @@ describe("loop.conversationViews", function () {
it("should render the PendingConversationView when the call state is 'init'",
function() {
store.set({callState: CALL_STATES.INIT});
store.set({
callState: CALL_STATES.INIT,
contact: contact
});
view = mountTestComponent();
@ -323,7 +346,10 @@ describe("loop.conversationViews", function () {
it("should update the rendered views when the state is changed.",
function() {
store.set({callState: CALL_STATES.INIT});
store.set({
callState: CALL_STATES.INIT,
contact: contact
});
view = mountTestComponent();

View File

@ -115,10 +115,24 @@ describe("loop.conversation", function() {
sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
new loop.shared.actions.GatherCallData({
calleeId: null,
callId: "42"
callId: "42",
outgoing: false
}));
});
it("should trigger an outgoing gatherCallData action for outgoing calls",
function() {
loop.shared.utils.Helper.prototype.locationHash.returns("#outgoing/24");
loop.conversation.init();
sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
new loop.shared.actions.GatherCallData({
callId: "24",
outgoing: true
}));
});
});
describe("ConversationControllerView", function() {
@ -141,7 +155,16 @@ describe("loop.conversation", function() {
sdk: {}
});
dispatcher = new loop.Dispatcher();
store = new loop.store.ConversationStore({}, {
store = new loop.store.ConversationStore({
contact: {
name: [ "Mr Smith" ],
email: [{
type: "home",
value: "fakeEmail",
pref: true
}]
}
}, {
client: client,
dispatcher: dispatcher,
sdkDriver: {}

View File

@ -11,6 +11,7 @@ describe("loop.ConversationStore", function () {
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sandbox, dispatcher, client, store, fakeSessionData, sdkDriver;
var contact;
var connectPromise, resolveConnectPromise, rejectConnectPromise;
var wsCancelSpy, wsCloseSpy, wsMediaUpSpy, fakeWebsocket;
@ -26,6 +27,15 @@ describe("loop.ConversationStore", function () {
beforeEach(function() {
sandbox = sinon.sandbox.create();
contact = {
name: [ "Mr Smith" ],
email: [{
type: "home",
value: "fakeEmail",
pref: true
}]
};
dispatcher = new loop.Dispatcher();
client = {
setupOutgoingCall: sinon.stub()
@ -199,13 +209,26 @@ describe("loop.ConversationStore", function () {
describe("#gatherCallData", function() {
beforeEach(function() {
store.set({callState: CALL_STATES.INIT});
navigator.mozLoop = {
getCallData: function() {
return {
contact: contact,
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO
};
}
};
});
afterEach(function() {
delete navigator.mozLoop;
});
it("should set the state to 'gather'", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
calleeId: "",
callId: "76543218"
callId: "76543218",
outgoing: true
}));
expect(store.get("callState")).eql(CALL_STATES.GATHER);
@ -214,22 +237,32 @@ describe("loop.ConversationStore", function () {
it("should save the basic call information", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
calleeId: "fake",
callId: "123456"
callId: "123456",
outgoing: true
}));
expect(store.get("calleeId")).eql("fake");
expect(store.get("callId")).eql("123456");
expect(store.get("outgoing")).eql(true);
});
it("should save the basic information from the mozLoop api", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
callId: "123456",
outgoing: true
}));
expect(store.get("contact")).eql(contact);
expect(store.get("callType")).eql(sharedUtils.CALL_TYPES.AUDIO_VIDEO);
});
describe("outgoing calls", function() {
var outgoingCallData;
beforeEach(function() {
outgoingCallData = {
calleeId: "fake",
callId: "135246"
callId: "123456",
outgoing: true
};
});
@ -239,7 +272,7 @@ describe("loop.ConversationStore", function () {
sinon.assert.calledOnce(client.setupOutgoingCall);
sinon.assert.calledWith(client.setupOutgoingCall,
["fake"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
["fakeEmail"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
});
describe("server response handling", function() {
@ -488,14 +521,14 @@ describe("loop.ConversationStore", function () {
callState: CALL_STATES.TERMINATED,
outgoing: true,
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO,
calleeId: "fake"
contact: contact
});
dispatcher.dispatch(new sharedActions.RetryCall());
sinon.assert.calledOnce(client.setupOutgoingCall);
sinon.assert.calledWith(client.setupOutgoingCall,
["fake"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
["fakeEmail"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
});
});
@ -518,7 +551,7 @@ describe("loop.ConversationStore", function () {
enabled: true
}));
expect(store.get("audioMuted")).eql(true);
expect(store.get("audioMuted")).eql(false);
});
it("should save the mute state for the video stream", function() {
@ -529,7 +562,7 @@ describe("loop.ConversationStore", function () {
enabled: false
}));
expect(store.get("videoMuted")).eql(false);
expect(store.get("videoMuted")).eql(true);
});
});

View File

@ -46,7 +46,7 @@ describe("loop.Dispatcher", function () {
beforeEach(function() {
gatherAction = new sharedActions.GatherCallData({
callId: "42",
calleeId: null
outgoing: false
});
cancelAction = new sharedActions.CancelCall();

View File

@ -0,0 +1,59 @@
/* 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/. */
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
let openChatOrig = Chat.open;
const contact = {
name: [ "Mr Smith" ],
email: [{
type: "home",
value: "fakeEmail",
pref: true
}]
};
add_task(function test_startDirectCall_opens_window() {
let openedUrl;
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
};
MozLoopService.startDirectCall(contact, "audio-video");
do_check_true(!!openedUrl, "should open a chat window");
// Stop the busy kicking in for following tests.
let callId = openedUrl.match(/about:loopconversation\#outgoing\/(.*)/)[1];
MozLoopService.releaseCallData(callId);
});
add_task(function test_startDirectCall_getCallData() {
let openedUrl;
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
};
MozLoopService.startDirectCall(contact, "audio-video");
let callId = openedUrl.match(/about:loopconversation\#outgoing\/(.*)/)[1];
let callData = MozLoopService.getCallData(callId);
do_check_eq(callData.callType, "audio-video", "should have the correct call type");
do_check_eq(callData.contact, contact, "should have the contact details");
// Stop the busy kicking in for following tests.
MozLoopService.releaseCallData(callId);
});
function run_test() {
do_register_cleanup(function() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
});
run_next_test();
}

View File

@ -5,6 +5,7 @@ firefox-appdir = browser
[test_loopapi_hawk_request.js]
[test_looppush_initialize.js]
[test_loopservice_directcall.js]
[test_loopservice_dnd.js]
[test_loopservice_expiry.js]
[test_loopservice_hawk_errors.js]
@ -17,4 +18,4 @@ firefox-appdir = browser
[test_loopservice_token_save.js]
[test_loopservice_token_send.js]
[test_loopservice_token_validation.js]
[test_loopservice_busy.js]
[test_loopservice_busy.js]