Bug 1048162 Part 1 - Add an 'Email Link' button to Loop desktop failed call view. r=Standard8

This commit is contained in:
Nicolas Perriault 2014-10-16 18:58:59 +01:00
Родитель 86289da8b5
Коммит 9b7f6776df
13 изменённых файлов: 312 добавлений и 78 удалений

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

@ -601,15 +601,18 @@ function injectLoopAPI(targetWindow) {
/** /**
* Composes an email via the external protocol service. * Composes an email via the external protocol service.
* *
* @param {String} subject Subject of the email to send * @param {String} subject Subject of the email to send
* @param {String} body Body message of the email to send * @param {String} body Body message of the email to send
* @param {String} recipient Recipient email address (optional)
*/ */
composeEmail: { composeEmail: {
enumerable: true, enumerable: true,
writable: true, writable: true,
value: function(subject, body) { value: function(subject, body, recipient) {
let mailtoURL = "mailto:?subject=" + encodeURIComponent(subject) + "&" + recipient = recipient || "";
"body=" + encodeURIComponent(body); let mailtoURL = "mailto:" + encodeURIComponent(recipient) +
"?subject=" + encodeURIComponent(subject) +
"&body=" + encodeURIComponent(body);
extProtocolSvc.loadURI(CommonUtils.makeURI(mailtoURL)); extProtocolSvc.loadURI(CommonUtils.makeURI(mailtoURL));
} }
}, },

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

@ -12,8 +12,20 @@ loop.conversationViews = (function(mozL10n) {
var CALL_STATES = loop.store.CALL_STATES; var CALL_STATES = loop.store.CALL_STATES;
var CALL_TYPES = loop.shared.utils.CALL_TYPES; var CALL_TYPES = loop.shared.utils.CALL_TYPES;
var sharedActions = loop.shared.actions; var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views; var sharedViews = loop.shared.views;
// 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.
function _getPreferredEmail(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];
}
/** /**
* Displays information about the call * Displays information about the call
* Caller avatar, name & conversation creation date * Caller avatar, name & conversation creation date
@ -93,17 +105,6 @@ loop.conversationViews = (function(mozL10n) {
contact: React.PropTypes.object 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() { render: function() {
var contactName; var contactName;
@ -111,7 +112,7 @@ loop.conversationViews = (function(mozL10n) {
this.props.contact.name[0]) { this.props.contact.name[0]) {
contactName = this.props.contact.name[0]; contactName = this.props.contact.name[0];
} else { } else {
contactName = this._getPreferredEmail(this.props.contact).value; contactName = _getPreferredEmail(this.props.contact).value;
} }
document.title = contactName; document.title = contactName;
@ -187,8 +188,33 @@ loop.conversationViews = (function(mozL10n) {
* Call failed view. Displayed when a call fails. * Call failed view. Displayed when a call fails.
*/ */
var CallFailedView = React.createClass({displayName: 'CallFailedView', var CallFailedView = React.createClass({displayName: 'CallFailedView',
mixins: [Backbone.Events],
propTypes: { propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
store: React.PropTypes.instanceOf(
loop.store.ConversationStore).isRequired,
contact: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {emailLinkButtonDisabled: false};
},
componentDidMount: function() {
this.listenTo(this.props.store, "change:emailLink",
this._onEmailLinkReceived);
},
componentWillUnmount: function() {
this.stopListening(this.props.store);
},
_onEmailLinkReceived: function() {
var emailLink = this.props.store.get("emailLink");
var contactEmail = _getPreferredEmail(this.props.contact).value;
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
window.close();
}, },
retryCall: function() { retryCall: function() {
@ -199,25 +225,33 @@ loop.conversationViews = (function(mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.CancelCall()); this.props.dispatcher.dispatch(new sharedActions.CancelCall());
}, },
emailLink: function() {
this.setState({emailLinkButtonDisabled: true});
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
},
render: function() { render: function() {
return ( return (
React.DOM.div({className: "call-window"}, 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.p({className: "btn-label"}, mozL10n.get("generic_failure_with_reason2")),
React.DOM.div({className: "btn-group call-action-group"}, 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: "btn btn-accept btn-retry", onClick: this.cancelCall},
onClick: this.retryCall}, mozL10n.get("cancel_button")
mozL10n.get("retry_call_button") ),
), React.DOM.button({className: "btn btn-info btn-retry",
React.DOM.div({className: "fx-embedded-call-button-spacer"}), onClick: this.retryCall},
React.DOM.button({className: "btn btn-cancel", mozL10n.get("retry_call_button")
onClick: this.cancelCall}, ),
mozL10n.get("cancel_button") React.DOM.button({className: "btn btn-info btn-email",
), onClick: this.emailLink,
React.DOM.div({className: "fx-embedded-call-button-spacer"}) disabled: this.state.emailLinkButtonDisabled},
mozL10n.get("share_button2")
)
) )
) )
); );
@ -425,7 +459,9 @@ loop.conversationViews = (function(mozL10n) {
} }
case CALL_STATES.TERMINATED: { case CALL_STATES.TERMINATED: {
return (CallFailedView({ return (CallFailedView({
dispatcher: this.props.dispatcher} dispatcher: this.props.dispatcher,
store: this.props.store,
contact: this.state.contact}
)); ));
} }
case CALL_STATES.ONGOING: { case CALL_STATES.ONGOING: {
@ -445,7 +481,7 @@ loop.conversationViews = (function(mozL10n) {
callState: this.state.callState, callState: this.state.callState,
contact: this.state.contact, contact: this.state.contact,
enableCancelButton: this._isCancellable()} enableCancelButton: this._isCancellable()}
)) ));
} }
} }
}, },

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

@ -12,8 +12,20 @@ loop.conversationViews = (function(mozL10n) {
var CALL_STATES = loop.store.CALL_STATES; var CALL_STATES = loop.store.CALL_STATES;
var CALL_TYPES = loop.shared.utils.CALL_TYPES; var CALL_TYPES = loop.shared.utils.CALL_TYPES;
var sharedActions = loop.shared.actions; var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views; var sharedViews = loop.shared.views;
// 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.
function _getPreferredEmail(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];
}
/** /**
* Displays information about the call * Displays information about the call
* Caller avatar, name & conversation creation date * Caller avatar, name & conversation creation date
@ -93,17 +105,6 @@ loop.conversationViews = (function(mozL10n) {
contact: React.PropTypes.object 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() { render: function() {
var contactName; var contactName;
@ -111,7 +112,7 @@ loop.conversationViews = (function(mozL10n) {
this.props.contact.name[0]) { this.props.contact.name[0]) {
contactName = this.props.contact.name[0]; contactName = this.props.contact.name[0];
} else { } else {
contactName = this._getPreferredEmail(this.props.contact).value; contactName = _getPreferredEmail(this.props.contact).value;
} }
document.title = contactName; document.title = contactName;
@ -187,8 +188,33 @@ loop.conversationViews = (function(mozL10n) {
* Call failed view. Displayed when a call fails. * Call failed view. Displayed when a call fails.
*/ */
var CallFailedView = React.createClass({ var CallFailedView = React.createClass({
mixins: [Backbone.Events],
propTypes: { propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
store: React.PropTypes.instanceOf(
loop.store.ConversationStore).isRequired,
contact: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {emailLinkButtonDisabled: false};
},
componentDidMount: function() {
this.listenTo(this.props.store, "change:emailLink",
this._onEmailLinkReceived);
},
componentWillUnmount: function() {
this.stopListening(this.props.store);
},
_onEmailLinkReceived: function() {
var emailLink = this.props.store.get("emailLink");
var contactEmail = _getPreferredEmail(this.props.contact).value;
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
window.close();
}, },
retryCall: function() { retryCall: function() {
@ -199,25 +225,33 @@ loop.conversationViews = (function(mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.CancelCall()); this.props.dispatcher.dispatch(new sharedActions.CancelCall());
}, },
emailLink: function() {
this.setState({emailLinkButtonDisabled: true});
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
},
render: function() { render: function() {
return ( return (
<div className="call-window"> <div className="call-window">
<h2>{mozL10n.get("generic_failure_title")}</h2> <h2>{mozL10n.get("generic_failure_title")}</h2>
<p className="btn-label">{mozL10n.get("generic_failure_no_reason2")}</p> <p className="btn-label">{mozL10n.get("generic_failure_with_reason2")}</p>
<div className="btn-group call-action-group"> <div className="btn-group call-action-group">
<div className="fx-embedded-call-button-spacer"></div> <button className="btn btn-cancel"
<button className="btn btn-accept btn-retry" onClick={this.cancelCall}>
onClick={this.retryCall}> {mozL10n.get("cancel_button")}
{mozL10n.get("retry_call_button")} </button>
</button> <button className="btn btn-info btn-retry"
<div className="fx-embedded-call-button-spacer"></div> onClick={this.retryCall}>
<button className="btn btn-cancel" {mozL10n.get("retry_call_button")}
onClick={this.cancelCall}> </button>
{mozL10n.get("cancel_button")} <button className="btn btn-info btn-email"
</button> onClick={this.emailLink}
<div className="fx-embedded-call-button-spacer"></div> disabled={this.state.emailLinkButtonDisabled}>
{mozL10n.get("share_button2")}
</button>
</div> </div>
</div> </div>
); );
@ -426,6 +460,8 @@ loop.conversationViews = (function(mozL10n) {
case CALL_STATES.TERMINATED: { case CALL_STATES.TERMINATED: {
return (<CallFailedView return (<CallFailedView
dispatcher={this.props.dispatcher} dispatcher={this.props.dispatcher}
store={this.props.store}
contact={this.state.contact}
/>); />);
} }
case CALL_STATES.ONGOING: { case CALL_STATES.ONGOING: {
@ -445,7 +481,7 @@ loop.conversationViews = (function(mozL10n) {
callState={this.state.callState} callState={this.state.callState}
contact={this.state.contact} contact={this.state.contact}
enableCancelButton={this._isCancellable()} enableCancelButton={this._isCancellable()}
/>) />);
} }
} }
}, },

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

@ -15,6 +15,7 @@ loop.panel = (function(_, mozL10n) {
var sharedModels = loop.shared.models; var sharedModels = loop.shared.models;
var sharedMixins = loop.shared.mixins; var sharedMixins = loop.shared.mixins;
var sharedActions = loop.shared.actions; var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var Button = sharedViews.Button; var Button = sharedViews.Button;
var ButtonGroup = sharedViews.ButtonGroup; var ButtonGroup = sharedViews.ButtonGroup;
var ContactsList = loop.contacts.ContactsList; var ContactsList = loop.contacts.ContactsList;
@ -362,11 +363,7 @@ loop.panel = (function(_, mozL10n) {
handleEmailButtonClick: function(event) { handleEmailButtonClick: function(event) {
this.handleLinkExfiltration(event); this.handleLinkExfiltration(event);
navigator.mozLoop.composeEmail( sharedUtils.composeCallUrlEmail(this.state.callUrl);
__("share_email_subject4", { clientShortname: __("clientShortname2")}),
__("share_email_body4", { callUrl: this.state.callUrl,
clientShortname: __("clientShortname2"),
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl") }));
}, },
handleCopyButtonClick: function(event) { handleCopyButtonClick: function(event) {

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

@ -15,6 +15,7 @@ loop.panel = (function(_, mozL10n) {
var sharedModels = loop.shared.models; var sharedModels = loop.shared.models;
var sharedMixins = loop.shared.mixins; var sharedMixins = loop.shared.mixins;
var sharedActions = loop.shared.actions; var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var Button = sharedViews.Button; var Button = sharedViews.Button;
var ButtonGroup = sharedViews.ButtonGroup; var ButtonGroup = sharedViews.ButtonGroup;
var ContactsList = loop.contacts.ContactsList; var ContactsList = loop.contacts.ContactsList;
@ -362,11 +363,7 @@ loop.panel = (function(_, mozL10n) {
handleEmailButtonClick: function(event) { handleEmailButtonClick: function(event) {
this.handleLinkExfiltration(event); this.handleLinkExfiltration(event);
navigator.mozLoop.composeEmail( sharedUtils.composeCallUrlEmail(this.state.callUrl);
__("share_email_subject4", { clientShortname: __("clientShortname2")}),
__("share_email_body4", { callUrl: this.state.callUrl,
clientShortname: __("clientShortname2"),
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl") }));
}, },
handleCopyButtonClick: function(event) { handleCopyButtonClick: function(event) {

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

@ -240,16 +240,21 @@
min-height: 230px; min-height: 230px;
} }
.call-window > .btn-label {
text-align: center;
}
.call-action-group { .call-action-group {
display: flex; display: flex;
padding: 2.5em 0 0 0; padding: 2.5em 4px 0 4px;
width: 100%; width: 100%;
justify-content: space-around;
} }
.call-action-group > .btn { .call-action-group > .btn {
margin-left: .5em;
height: 26px; height: 26px;
border-radius: 2px;
margin: 0 4px;
min-width: 64px;
} }
.call-action-group .btn-group-chevron, .call-action-group .btn-group-chevron,

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

@ -30,6 +30,13 @@ loop.shared.actions = (function() {
}; };
return { return {
/**
* Fetch a new call url from the server, intended to be sent over email when
* a contact can't be reached.
*/
FetchEmailLink: Action.define("fetchEmailLink", {
}),
/** /**
* Used to trigger gathering of initial call data. * Used to trigger gathering of initial call data.
*/ */

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

@ -126,7 +126,8 @@ loop.store.ConversationStore = (function() {
"cancelCall", "cancelCall",
"retryCall", "retryCall",
"mediaConnected", "mediaConnected",
"setMute" "setMute",
"fetchEmailLink"
]); ]);
}, },
@ -303,6 +304,23 @@ loop.store.ConversationStore = (function() {
this.set(muteType, !actionData.enabled); this.set(muteType, !actionData.enabled);
}, },
/**
* Fetches a new call 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) {
if (err) {
// XXX better error reporting in the UI
console.error(err);
return;
}
this.set("emailLink", callUrlData.callUrl);
}.bind(this));
},
/** /**
* Obtains the outgoing call data from the server and handles the * Obtains the outgoing call data from the server and handles the
* result. * result.

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

@ -6,7 +6,7 @@
var loop = loop || {}; var loop = loop || {};
loop.shared = loop.shared || {}; loop.shared = loop.shared || {};
loop.shared.utils = (function() { loop.shared.utils = (function(mozL10n) {
"use strict"; "use strict";
/** /**
@ -96,11 +96,37 @@ loop.shared.utils = (function() {
} }
}; };
/**
* Generates and opens a mailto: url with call URL information prefilled.
* Note: This only works for Desktop.
*
* @param {String} callUrl The call URL.
* @param {String} recipient The recipient email address (optional).
*/
function composeCallUrlEmail(callUrl, recipient) {
if (typeof navigator.mozLoop === "undefined") {
console.warn("composeCallUrlEmail isn't available for Loop standalone.");
return;
}
navigator.mozLoop.composeEmail(
mozL10n.get("share_email_subject4", {
clientShortname: mozL10n.get("clientShortname2")
}),
mozL10n.get("share_email_body4", {
callUrl: callUrl,
clientShortname: mozL10n.get("clientShortname2"),
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl")
}),
recipient
);
}
return { return {
CALL_TYPES: CALL_TYPES, CALL_TYPES: CALL_TYPES,
Helper: Helper, Helper: Helper,
composeCallUrlEmail: composeCallUrlEmail,
formatDate: formatDate, formatDate: formatDate,
getTargetPlatform: getTargetPlatform, getTargetPlatform: getTargetPlatform,
getBoolPreference: getBoolPreference getBoolPreference: getBoolPreference
}; };
})(); })(document.mozL10n || navigator.mozL10n);

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

@ -6,6 +6,7 @@ var expect = chai.expect;
describe("loop.conversationViews", function () { describe("loop.conversationViews", function () {
"use strict"; "use strict";
var sharedUtils = loop.shared.utils;
var sandbox, oldTitle, view, dispatcher, contact; var sandbox, oldTitle, view, dispatcher, contact;
var CALL_STATES = loop.store.CALL_STATES; var CALL_STATES = loop.store.CALL_STATES;
@ -201,13 +202,25 @@ describe("loop.conversationViews", function () {
}); });
describe("CallFailedView", function() { describe("CallFailedView", function() {
var store;
function mountTestComponent(props) { function mountTestComponent(props) {
return TestUtils.renderIntoDocument( return TestUtils.renderIntoDocument(
loop.conversationViews.CallFailedView({ loop.conversationViews.CallFailedView({
dispatcher: dispatcher dispatcher: dispatcher,
store: store,
contact: {email: [{value: "test@test.tld"}]}
})); }));
} }
beforeEach(function() {
store = new loop.store.ConversationStore({}, {
dispatcher: dispatcher,
client: {},
sdkDriver: {}
});
});
it("should dispatch a retryCall action when the retry button is pressed", it("should dispatch a retryCall action when the retry button is pressed",
function() { function() {
view = mountTestComponent(); view = mountTestComponent();
@ -233,6 +246,48 @@ describe("loop.conversationViews", function () {
sinon.assert.calledWithMatch(dispatcher.dispatch, sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "cancelCall")); sinon.match.hasOwn("name", "cancelCall"));
}); });
it("should dispatch a fetchEmailLink action when the cancel button is pressed",
function() {
view = mountTestComponent();
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("name", "fetchEmailLink"));
});
it("should disable the email link button once the action is dispatched",
function() {
view = mountTestComponent();
var emailLinkBtn = view.getDOMNode().querySelector('.btn-email');
React.addons.TestUtils.Simulate.click(emailLinkBtn);
expect(view.getDOMNode().querySelector(".btn-email").disabled).eql(true);
});
it("should compose an email once the email link is received", function() {
var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
view = mountTestComponent();
store.set("emailLink", "http://fake.invalid/");
sinon.assert.calledOnce(composeCallUrlEmail);
sinon.assert.calledWithExactly(composeCallUrlEmail,
"http://fake.invalid/", "test@test.tld");
});
it("should close the conversation window once the email link is received",
function() {
sandbox.stub(window, "close");
view = mountTestComponent();
store.set("emailLink", "http://fake.invalid/");
sinon.assert.calledOnce(window.close);
});
}); });
describe("OngoingConversationView", function() { describe("OngoingConversationView", function() {

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

@ -8,6 +8,7 @@
var expect = chai.expect; var expect = chai.expect;
var TestUtils = React.addons.TestUtils; var TestUtils = React.addons.TestUtils;
var sharedActions = loop.shared.actions; var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
describe("loop.panel", function() { describe("loop.panel", function() {
"use strict"; "use strict";
@ -449,6 +450,7 @@ describe("loop.panel", function() {
it("should display a share button for email", function() { it("should display a share button for email", function() {
fakeClient.requestCallUrl = sandbox.stub(); fakeClient.requestCallUrl = sandbox.stub();
var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({ var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
notifications: notifications, notifications: notifications,
client: fakeClient client: fakeClient
@ -457,7 +459,9 @@ describe("loop.panel", function() {
TestUtils.findRenderedDOMComponentWithClass(view, "button-email"); TestUtils.findRenderedDOMComponentWithClass(view, "button-email");
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email")); TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
sinon.assert.calledOnce(navigator.mozLoop.composeEmail);
sinon.assert.calledOnce(composeCallUrlEmail);
sinon.assert.calledWithExactly(composeCallUrlEmail, "http://example.com");
}); });
it("should feature a copy button capable of copying the call url when clicked", function() { it("should feature a copy button capable of copying the call url when clicked", function() {

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

@ -38,7 +38,8 @@ describe("loop.store.ConversationStore", function () {
dispatcher = new loop.Dispatcher(); dispatcher = new loop.Dispatcher();
client = { client = {
setupOutgoingCall: sinon.stub() setupOutgoingCall: sinon.stub(),
requestCallUrl: sinon.stub()
}; };
sdkDriver = { sdkDriver = {
connectSession: sinon.stub(), connectSession: sinon.stub(),
@ -566,6 +567,28 @@ describe("loop.store.ConversationStore", function () {
}); });
}); });
describe("#fetchEmailLink", function() {
it("should request a new call url to the server", function() {
dispatcher.dispatch(new sharedActions.FetchEmailLink());
sinon.assert.calledOnce(client.requestCallUrl);
sinon.assert.calledWith(client.requestCallUrl, "");
});
it("should update the emailLink attribute when the new call url is received",
function() {
client.requestCallUrl = function(callId, cb) {
cb(null, {callUrl: "http://fake.invalid/"});
};
dispatcher.dispatch(new sharedActions.FetchEmailLink());
expect(store.get("emailLink")).eql("http://fake.invalid/");
});
// XXX bug 1048162 Part 2
it.skip("should trigger an error in case of failure");
});
describe("Events", function() { describe("Events", function() {
describe("Websocket progress", function() { describe("Websocket progress", function() {
beforeEach(function() { beforeEach(function() {

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

@ -18,6 +18,7 @@ describe("loop.shared.utils", function() {
}); });
afterEach(function() { afterEach(function() {
navigator.mozLoop = undefined;
sandbox.restore(); sandbox.restore();
}); });
@ -110,7 +111,6 @@ describe("loop.shared.utils", function() {
describe("#getBoolPreference", function() { describe("#getBoolPreference", function() {
afterEach(function() { afterEach(function() {
navigator.mozLoop = undefined;
localStorage.removeItem("test.true"); localStorage.removeItem("test.true");
}); });
@ -142,4 +142,31 @@ describe("loop.shared.utils", function() {
}); });
}); });
}); });
describe("#composeCallUrlEmail", function() {
var composeEmail;
beforeEach(function() {
// fake mozL10n
sandbox.stub(navigator.mozL10n, "get", function(id) {
switch(id) {
case "share_email_subject4": return "subject";
case "share_email_body4": return "body";
}
});
composeEmail = sandbox.spy();
navigator.mozLoop = {
getLoopCharPref: sandbox.spy(),
composeEmail: composeEmail
};
});
it("should compose a call url email", function() {
sharedUtils.composeCallUrlEmail("http://invalid", "fake@invalid.tld");
sinon.assert.calledOnce(composeEmail);
sinon.assert.calledWith(composeEmail,
"subject", "body", "fake@invalid.tld");
});
});
}); });