Bug 1102170 - Share a room url by email when Loop direct call fails. r=Standard8

This commit is contained in:
Nicolas Perriault 2014-12-10 22:51:53 +01:00
Родитель 437abd21ee
Коммит db9bde32fb
7 изменённых файлов: 120 добавлений и 54 удалений

Просмотреть файл

@ -27,6 +27,13 @@ loop.conversationViews = (function(mozL10n) {
return contact.email.find(e => e.pref) || contact.email[0];
}
function _getContactDisplayName(contact) {
if (contact.name && contact.name[0]) {
return contact.name[0];
}
return _getPreferredEmail(contact).value;
}
/**
* Displays information about the call
* Caller avatar, name & conversation creation date
@ -107,14 +114,7 @@ loop.conversationViews = (function(mozL10n) {
},
render: function() {
var contactName;
if (this.props.contact.name &&
this.props.contact.name[0]) {
contactName = this.props.contact.name[0];
} else {
contactName = _getPreferredEmail(this.props.contact).value;
}
var contactName = _getContactDisplayName(this.props.contact);
document.title = contactName;
@ -262,7 +262,10 @@ loop.conversationViews = (function(mozL10n) {
emailLinkButtonDisabled: true
});
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
this.props.dispatcher.dispatch(new sharedActions.FetchRoomEmailLink({
roomOwner: navigator.mozLoop.userProfile.email,
roomName: _getContactDisplayName(this.props.contact)
}));
},
render: function() {

Просмотреть файл

@ -27,6 +27,13 @@ loop.conversationViews = (function(mozL10n) {
return contact.email.find(e => e.pref) || contact.email[0];
}
function _getContactDisplayName(contact) {
if (contact.name && contact.name[0]) {
return contact.name[0];
}
return _getPreferredEmail(contact).value;
}
/**
* Displays information about the call
* Caller avatar, name & conversation creation date
@ -107,14 +114,7 @@ loop.conversationViews = (function(mozL10n) {
},
render: function() {
var contactName;
if (this.props.contact.name &&
this.props.contact.name[0]) {
contactName = this.props.contact.name[0];
} else {
contactName = _getPreferredEmail(this.props.contact).value;
}
var contactName = _getContactDisplayName(this.props.contact);
document.title = contactName;
@ -262,7 +262,10 @@ loop.conversationViews = (function(mozL10n) {
emailLinkButtonDisabled: true
});
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
this.props.dispatcher.dispatch(new sharedActions.FetchRoomEmailLink({
roomOwner: navigator.mozLoop.userProfile.email,
roomName: _getContactDisplayName(this.props.contact)
}));
},
render: function() {

Просмотреть файл

@ -76,10 +76,12 @@ loop.shared.actions = (function() {
}),
/**
* Fetch a new call url from the server, intended to be sent over email when
* Fetch a new room url from the server, intended to be sent over email when
* a contact can't be reached.
*/
FetchEmailLink: Action.define("fetchEmailLink", {
FetchRoomEmailLink: Action.define("fetchRoomEmailLink", {
roomOwner: String,
roomName: String
}),
/**

Просмотреть файл

@ -210,7 +210,7 @@ loop.store = loop.store || {};
"retryCall",
"mediaConnected",
"setMute",
"fetchEmailLink"
"fetchRoomEmailLink"
]);
this.setStoreState({
@ -323,18 +323,21 @@ loop.store = loop.store || {};
},
/**
* Fetches a new call URL intended to be sent over email when a contact
* Fetches a new room URL intended to be sent over email when a contact
* can't be reached.
*/
fetchEmailLink: function() {
// XXX This is an empty string as a conversation identifier. Bug 1015938 implements
// a user-set string.
this.client.requestCallUrl("", function(err, callUrlData) {
fetchRoomEmailLink: function(actionData) {
this.mozLoop.rooms.create({
roomName: actionData.roomName,
roomOwner: actionData.roomOwner,
maxSize: loop.store.MAX_ROOM_CREATION_SIZE,
expiresIn: loop.store.DEFAULT_EXPIRES_IN
}, function(err, createdRoomData) {
if (err) {
this.trigger("error:emailLink");
return;
}
this.setStoreState({"emailLink": callUrlData.callUrl});
this.setStoreState({"emailLink": createdRoomData.roomUrl});
}.bind(this));
},

Просмотреть файл

@ -16,6 +16,20 @@ loop.store = loop.store || {};
*/
var sharedActions = loop.shared.actions;
/**
* Maximum size given to createRoom; only 2 is supported (and is
* always passed) because that's what the user-experience is currently
* designed and tested to handle.
* @type {Number}
*/
var MAX_ROOM_CREATION_SIZE = loop.store.MAX_ROOM_CREATION_SIZE = 2;
/**
* The number of hours for which the room will exist - default 8 weeks
* @type {Number}
*/
var DEFAULT_EXPIRES_IN = loop.store.DEFAULT_EXPIRES_IN = 24 * 7 * 8;
/**
* Room validation schema. See validate.js.
* @type {Object}
@ -61,13 +75,13 @@ loop.store = loop.store || {};
* designed and tested to handle.
* @type {Number}
*/
maxRoomCreationSize: 2,
maxRoomCreationSize: MAX_ROOM_CREATION_SIZE,
/**
* The number of hours for which the room will exist - default 8 weeks
* @type {Number}
*/
defaultExpiresIn: 24 * 7 * 8,
defaultExpiresIn: DEFAULT_EXPIRES_IN,
/**
* Registered actions.

Просмотреть файл

@ -56,7 +56,10 @@ describe("loop.conversationViews", function () {
},
getAudioBlob: sinon.spy(function(name, callback) {
callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"}));
})
}),
userProfile: {
email: "bob@invalid.tld"
}
};
fakeWindow = {
@ -241,12 +244,15 @@ describe("loop.conversationViews", function () {
describe("CallFailedView", function() {
var store, fakeAudio;
function mountTestComponent(props) {
var contact = {email: [{value: "test@test.tld"}]};
function mountTestComponent(options) {
options = options || {};
return TestUtils.renderIntoDocument(
loop.conversationViews.CallFailedView({
dispatcher: dispatcher,
store: store,
contact: {email: [{value: "test@test.tld"}]}
contact: options.contact
}));
}
@ -266,7 +272,7 @@ describe("loop.conversationViews", function () {
it("should dispatch a retryCall action when the retry button is pressed",
function() {
view = mountTestComponent();
view = mountTestComponent({contact: contact});
var retryBtn = view.getDOMNode().querySelector('.btn-retry');
@ -279,7 +285,7 @@ describe("loop.conversationViews", function () {
it("should dispatch a cancelCall action when the cancel button is pressed",
function() {
view = mountTestComponent();
view = mountTestComponent({contact: contact});
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
@ -290,9 +296,9 @@ describe("loop.conversationViews", function () {
sinon.match.hasOwn("name", "cancelCall"));
});
it("should dispatch a fetchEmailLink action when the cancel button is pressed",
it("should dispatch a fetchRoomEmailLink action when the email button is pressed",
function() {
view = mountTestComponent();
view = mountTestComponent({contact: contact});
var emailLinkBtn = view.getDOMNode().querySelector('.btn-email');
@ -300,12 +306,32 @@ describe("loop.conversationViews", function () {
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "fetchEmailLink"));
sinon.match.hasOwn("name", "fetchRoomEmailLink"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("roomOwner", fakeMozLoop.userProfile.email));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("roomName", "test@test.tld"));
});
it("should name the created room using the contact name when available",
function() {
view = mountTestComponent({contact: {
email: [{value: "test@test.tld"}],
name: ["Mr Fake ContactName"]
}});
var emailLinkBtn = view.getDOMNode().querySelector('.btn-email');
React.addons.TestUtils.Simulate.click(emailLinkBtn);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("roomName", "Mr Fake ContactName"));
});
it("should disable the email link button once the action is dispatched",
function() {
view = mountTestComponent();
view = mountTestComponent({contact: contact});
var emailLinkBtn = view.getDOMNode().querySelector('.btn-email');
React.addons.TestUtils.Simulate.click(emailLinkBtn);
@ -314,7 +340,7 @@ describe("loop.conversationViews", function () {
it("should compose an email once the email link is received", function() {
var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
view = mountTestComponent();
view = mountTestComponent({contact: contact});
store.setStoreState({emailLink: "http://fake.invalid/"});
sinon.assert.calledOnce(composeCallUrlEmail);
@ -324,7 +350,7 @@ describe("loop.conversationViews", function () {
it("should close the conversation window once the email link is received",
function() {
view = mountTestComponent();
view = mountTestComponent({contact: contact});
store.setStoreState({emailLink: "http://fake.invalid/"});
@ -333,7 +359,7 @@ describe("loop.conversationViews", function () {
it("should display an error message in case email link retrieval failed",
function() {
view = mountTestComponent();
view = mountTestComponent({contact: contact});
store.trigger("error:emailLink");
@ -342,7 +368,7 @@ describe("loop.conversationViews", function () {
it("should allow retrying to get a call url if it failed previously",
function() {
view = mountTestComponent();
view = mountTestComponent({contact: contact});
store.trigger("error:emailLink");
@ -350,7 +376,7 @@ describe("loop.conversationViews", function () {
});
it("should play a failure sound, once", function() {
view = mountTestComponent();
view = mountTestComponent({contact: contact});
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,

Просмотреть файл

@ -42,6 +42,9 @@ describe("loop.store.ConversationStore", function () {
calls: {
setCallInProgress: sandbox.stub(),
clearCallInProgress: sandbox.stub()
},
rooms: {
create: sandbox.stub()
}
};
@ -701,20 +704,29 @@ describe("loop.store.ConversationStore", function () {
});
});
describe("#fetchEmailLink", function() {
describe("#fetchRoomEmailLink", function() {
it("should request a new call url to the server", function() {
store.fetchEmailLink(new sharedActions.FetchEmailLink());
store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({
roomOwner: "bob@invalid.tld",
roomName: "FakeRoomName"
}));
sinon.assert.calledOnce(client.requestCallUrl);
sinon.assert.calledWith(client.requestCallUrl, "");
sinon.assert.calledOnce(fakeMozLoop.rooms.create);
sinon.assert.calledWithMatch(fakeMozLoop.rooms.create, {
roomOwner: "bob@invalid.tld",
roomName: "FakeRoomName"
});
});
it("should update the emailLink attribute when the new call url is received",
it("should update the emailLink attribute when the new room url is received",
function() {
client.requestCallUrl = function(callId, cb) {
cb(null, {callUrl: "http://fake.invalid/"});
fakeMozLoop.rooms.create = function(roomData, cb) {
cb(null, {roomUrl: "http://fake.invalid/"});
};
store.fetchEmailLink(new sharedActions.FetchEmailLink());
store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({
roomOwner: "bob@invalid.tld",
roomName: "FakeRoomName"
}));
expect(store.getStoreState("emailLink")).eql("http://fake.invalid/");
});
@ -722,10 +734,13 @@ describe("loop.store.ConversationStore", function () {
it("should trigger an error:emailLink event in case of failure",
function() {
var trigger = sandbox.stub(store, "trigger");
client.requestCallUrl = function(callId, cb) {
cb("error");
fakeMozLoop.rooms.create = function(roomData, cb) {
cb(new Error("error"));
};
store.fetchEmailLink(new sharedActions.FetchEmailLink());
store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({
roomOwner: "bob@invalid.tld",
roomName: "FakeRoomName"
}));
sinon.assert.calledOnce(trigger);
sinon.assert.calledWithExactly(trigger, "error:emailLink");