Bug 1121071 Part 1 - Remove old call url code from the Loop panel and related UI areas. r=mikedeboer

This commit is contained in:
Mark Banner 2015-01-16 18:34:30 +00:00
Родитель 68d3ae3040
Коммит bfc73a696e
16 изменённых файлов: 62 добавлений и 1091 удалений

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

@ -1677,7 +1677,6 @@ pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data:
#endif
pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
pref("loop.rooms.enabled", true);
pref("loop.fxa_oauth.tokendata", "");
pref("loop.fxa_oauth.profile", "");
pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");

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

@ -416,26 +416,6 @@ function injectLoopAPI(targetWindow) {
}
},
/**
* Used to note a call url expiry time. If the time is later than the current
* latest expiry time, then the stored expiry time is increased. For times
* sooner, this function is a no-op; this ensures we always have the latest
* expiry time for a url.
*
* This is used to determine whether or not we should be registering with the
* push server on start.
*
* @param {Integer} expiryTimeSeconds The seconds since epoch of the expiry time
* of the url.
*/
noteCallUrlExpiry: {
enumerable: true,
writable: true,
value: function(expiryTimeSeconds) {
MozLoopService.noteCallUrlExpiry(expiryTimeSeconds);
}
},
/**
* Set any preference under "loop."
*

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

@ -1243,22 +1243,6 @@ this.MozLoopService = {
return MozLoopServiceInternal.promiseRegisteredWithServers(sessionType);
},
/**
* Used to note a call url expiry time. If the time is later than the current
* latest expiry time, then the stored expiry time is increased. For times
* sooner, this function is a no-op; this ensures we always have the latest
* expiry time for a url.
*
* This is used to determine whether or not we should be registering with the
* push server on start.
*
* @param {Integer} expiryTimeSeconds The seconds since epoch of the expiry time
* of the url.
*/
noteCallUrlExpiry: function(expiryTimeSeconds) {
MozLoopServiceInternal.expiryTimeSeconds = expiryTimeSeconds;
},
/**
* Returns the strings for the specified element. Designed for use with l10n.js.
*

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

@ -9,12 +9,6 @@ var loop = loop || {};
loop.Client = (function($) {
"use strict";
// The expected properties to be returned from the POST /call-url/ request.
var expectedCallUrlProperties = ["callUrl", "expiresAt"];
// The expected properties to be returned from the GET /calls request.
var expectedCallProperties = ["calls"];
// THe expected properties to be returned from the POST /calls request.
var expectedPostCallProperties = [
"apiKey", "callId", "progressURL",
@ -81,56 +75,6 @@ loop.Client = (function($) {
cb(error);
},
/**
* Requests a call URL from the Loop server. It will note the
* expiry time for the url with the mozLoop api. It will select the
* appropriate hawk session to use based on whether or not the user
* is currently logged into a Firefox account profile.
*
* Callback parameters:
* - err null on successful request, non-null otherwise.
* - callUrlData an object of the obtained call url data if successful:
* -- callUrl: The url of the call
* -- expiresAt: The amount of hours until expiry of the url
*
* @param {String} simplepushUrl a registered Simple Push URL
* @param {string} nickname the nickname of the future caller
* @param {Function} cb Callback(err, callUrlData)
*/
requestCallUrl: function(nickname, cb) {
var sessionType;
if (this.mozLoop.userProfile) {
sessionType = this.mozLoop.LOOP_SESSION_TYPE.FXA;
} else {
sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST;
}
this.mozLoop.hawkRequest(sessionType, "/call-url/", "POST",
{callerId: nickname},
function (error, responseText) {
if (error) {
this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
this._failureHandler(cb, error);
return;
}
try {
var urlData = JSON.parse(responseText);
// This throws if the data is invalid, in which case only the failure
// telemetry will be recorded.
var returnData = this._validate(urlData, expectedCallUrlProperties);
this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", true);
cb(null, returnData);
} catch (err) {
this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
console.log("Error requesting call info", err);
cb(err);
}
}.bind(this));
},
/**
* Block call URL based on the token identifier
*
@ -203,20 +147,6 @@ loop.Client = (function($) {
}.bind(this)
);
},
/**
* Adds a value to a telemetry histogram, ignoring errors.
*
* @param {string} histogramId Name of the telemetry histogram to update.
* @param {integer} value Value to add to the histogram.
*/
_telemetryAdd: function(histogramId, value) {
try {
this.mozLoop.telemetryAdd(histogramId, value);
} catch (err) {
console.error("Error recording telemetry", err);
}
},
};
return Client;

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

@ -39,9 +39,7 @@ loop.panel = (function(_, mozL10n) {
// When we don't need to rely on the pref, this can move back to
// getDefaultProps (bug 1100258).
return {
selectedTab: this.props.selectedTab ||
(navigator.mozLoop.getLoopPref("rooms.enabled") ?
"rooms" : "call")
selectedTab: this.props.selectedTab || "rooms"
};
},
@ -358,157 +356,6 @@ loop.panel = (function(_, mozL10n) {
}
});
/**
* Call url result view.
*/
var CallUrlResult = React.createClass({displayName: "CallUrlResult",
mixins: [sharedMixins.DocumentVisibilityMixin],
propTypes: {
callUrl: React.PropTypes.string,
callUrlExpiry: React.PropTypes.number,
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
pending: false,
copied: false,
callUrl: this.props.callUrl || "",
callUrlExpiry: 0
};
},
/**
* Provided by DocumentVisibilityMixin. Schedules retrieval of a new call
* URL everytime the panel is reopened.
*/
onDocumentVisible: function() {
this._fetchCallUrl();
},
componentDidMount: function() {
// If we've already got a callURL, don't bother requesting a new one.
// As of this writing, only used for visual testing in the UI showcase.
if (this.state.callUrl.length) {
return;
}
this._fetchCallUrl();
},
/**
* Fetches a call URL.
*/
_fetchCallUrl: function() {
this.setState({pending: true});
// XXX This is an empty string as a conversation identifier. Bug 1015938 implements
// a user-set string.
this.props.client.requestCallUrl("",
this._onCallUrlReceived);
},
_onCallUrlReceived: function(err, callUrlData) {
if (err) {
if (err.code != 401) {
// 401 errors are already handled in hawkRequest and show an error
// message about the session.
this.props.notifications.errorL10n("unable_retrieve_url");
}
this.setState(this.getInitialState());
} else {
try {
var callUrl = new window.URL(callUrlData.callUrl);
// XXX the current server vers does not implement the callToken field
// but it exists in the API. This workaround should be removed in the future
var token = callUrlData.callToken ||
callUrl.pathname.split('/').pop();
// Now that a new URL is available, indicate it has not been shared.
this.linkExfiltrated = false;
this.setState({pending: false, copied: false,
callUrl: callUrl.href,
callUrlExpiry: callUrlData.expiresAt});
} catch(e) {
console.log(e);
this.props.notifications.errorL10n("unable_retrieve_url");
this.setState(this.getInitialState());
}
}
},
handleEmailButtonClick: function(event) {
this.handleLinkExfiltration(event);
sharedUtils.composeCallUrlEmail(this.state.callUrl);
},
handleCopyButtonClick: function(event) {
this.handleLinkExfiltration(event);
// XXX the mozLoop object should be passed as a prop, to ease testing and
// using a fake implementation in UI components showcase.
navigator.mozLoop.copyString(this.state.callUrl);
this.setState({copied: true});
},
linkExfiltrated: false,
handleLinkExfiltration: function(event) {
// Update the count of shared URLs only once per generated URL.
if (!this.linkExfiltrated) {
this.linkExfiltrated = true;
try {
navigator.mozLoop.telemetryAdd("LOOP_CLIENT_CALL_URL_SHARED", true);
} catch (err) {
console.error("Error recording telemetry", err);
}
}
// Note URL expiration every time it is shared.
if (this.state.callUrlExpiry) {
navigator.mozLoop.noteCallUrlExpiry(this.state.callUrlExpiry);
}
},
render: function() {
// XXX setting elem value from a state (in the callUrl input)
// makes it immutable ie read only but that is fine in our case.
// readOnly attr will suppress a warning regarding this issue
// from the react lib.
var cx = React.addons.classSet;
return (
React.createElement("div", {className: "generate-url"},
React.createElement("header", {id: "share-link-header"}, mozL10n.get("share_link_header_text")),
React.createElement("div", {className: "generate-url-stack"},
React.createElement("input", {type: "url", value: this.state.callUrl, readOnly: "true",
onCopy: this.handleLinkExfiltration,
className: cx({"generate-url-input": true,
pending: this.state.pending,
// Used in functional testing, signals that
// call url was received from loop server
callUrl: !this.state.pending})}),
React.createElement("div", {className: cx({"generate-url-spinner": true,
spinner: true,
busy: this.state.pending})})
),
React.createElement(ButtonGroup, {additionalClass: "url-actions"},
React.createElement(Button, {additionalClass: "button-email",
disabled: !this.state.callUrl,
onClick: this.handleEmailButtonClick,
caption: mozL10n.get("share_button")}),
React.createElement(Button, {additionalClass: "button-copy",
disabled: !this.state.callUrl,
onClick: this.handleCopyButtonClick,
caption: this.state.copied ? mozL10n.get("copied_url_button") :
mozL10n.get("copy_url_button")})
)
)
);
}
});
/**
* FxA sign in/up link component.
*/
@ -820,9 +667,7 @@ loop.panel = (function(_, mozL10n) {
var PanelView = React.createClass({displayName: "PanelView",
propTypes: {
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired,
// Mostly used for UI components showcase and unit tests
callUrl: React.PropTypes.string,
userProfile: React.PropTypes.object,
// Used only for unit tests.
showTabButtons: React.PropTypes.bool,
@ -869,17 +714,13 @@ loop.panel = (function(_, mozL10n) {
}
},
_roomsEnabled: function() {
return this.props.mozLoop.getLoopPref("rooms.enabled");
},
_onStatusChanged: function() {
var profile = this.props.mozLoop.userProfile;
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
var newUid = profile ? profile.uid : null;
if (currUid != newUid) {
// On profile change (login, logout), switch back to the default tab.
this.selectTab(this._roomsEnabled() ? "rooms" : "call");
this.selectTab("rooms");
this.setState({userProfile: profile});
}
this.updateServiceErrors();
@ -902,34 +743,6 @@ loop.panel = (function(_, mozL10n) {
}
},
/**
* The rooms feature is hidden by default for now. Once it gets mainstream,
* this method can be simplified.
*/
_renderRoomsOrCallTab: function() {
if (!this._roomsEnabled()) {
return (
React.createElement(Tab, {name: "call"},
React.createElement("div", {className: "content-area"},
React.createElement(CallUrlResult, {client: this.props.client,
notifications: this.props.notifications,
callUrl: this.props.callUrl}),
React.createElement(ToSView, null)
)
)
);
}
return (
React.createElement(Tab, {name: "rooms"},
React.createElement(RoomList, {dispatcher: this.props.dispatcher,
store: this.props.roomStore,
userDisplayName: this._getUserDisplayName()}),
React.createElement(ToSView, null)
)
);
},
startForm: function(name, contact) {
this.refs[name].initForm(contact);
this.selectTab(name);
@ -986,7 +799,12 @@ loop.panel = (function(_, mozL10n) {
clearOnDocumentHidden: true}),
React.createElement(TabView, {ref: "tabView", selectedTab: this.props.selectedTab,
buttonsHidden: hideButtons},
this._renderRoomsOrCallTab(),
React.createElement(Tab, {name: "rooms"},
React.createElement(RoomList, {dispatcher: this.props.dispatcher,
store: this.props.roomStore,
userDisplayName: this._getUserDisplayName()}),
React.createElement(ToSView, null)
),
React.createElement(Tab, {name: "contacts"},
React.createElement(ContactsList, {selectTab: this.selectTab,
startForm: this.startForm,
@ -1029,7 +847,6 @@ loop.panel = (function(_, mozL10n) {
// else to ensure the L10n environment is setup correctly.
mozL10n.initialize(navigator.mozLoop);
var client = new loop.Client();
var notifications = new sharedModels.NotificationCollection();
var dispatcher = new loop.Dispatcher();
var roomStore = new loop.store.RoomStore(dispatcher, {
@ -1038,7 +855,6 @@ loop.panel = (function(_, mozL10n) {
});
React.render(React.createElement(PanelView, {
client: client,
notifications: notifications,
roomStore: roomStore,
mozLoop: navigator.mozLoop,
@ -1057,7 +873,6 @@ loop.panel = (function(_, mozL10n) {
init: init,
AuthLink: AuthLink,
AvailabilityDropdown: AvailabilityDropdown,
CallUrlResult: CallUrlResult,
GettingStartedView: GettingStartedView,
PanelView: PanelView,
RoomEntry: RoomEntry,

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

@ -39,9 +39,7 @@ loop.panel = (function(_, mozL10n) {
// When we don't need to rely on the pref, this can move back to
// getDefaultProps (bug 1100258).
return {
selectedTab: this.props.selectedTab ||
(navigator.mozLoop.getLoopPref("rooms.enabled") ?
"rooms" : "call")
selectedTab: this.props.selectedTab || "rooms"
};
},
@ -358,157 +356,6 @@ loop.panel = (function(_, mozL10n) {
}
});
/**
* Call url result view.
*/
var CallUrlResult = React.createClass({
mixins: [sharedMixins.DocumentVisibilityMixin],
propTypes: {
callUrl: React.PropTypes.string,
callUrlExpiry: React.PropTypes.number,
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
pending: false,
copied: false,
callUrl: this.props.callUrl || "",
callUrlExpiry: 0
};
},
/**
* Provided by DocumentVisibilityMixin. Schedules retrieval of a new call
* URL everytime the panel is reopened.
*/
onDocumentVisible: function() {
this._fetchCallUrl();
},
componentDidMount: function() {
// If we've already got a callURL, don't bother requesting a new one.
// As of this writing, only used for visual testing in the UI showcase.
if (this.state.callUrl.length) {
return;
}
this._fetchCallUrl();
},
/**
* Fetches a call URL.
*/
_fetchCallUrl: function() {
this.setState({pending: true});
// XXX This is an empty string as a conversation identifier. Bug 1015938 implements
// a user-set string.
this.props.client.requestCallUrl("",
this._onCallUrlReceived);
},
_onCallUrlReceived: function(err, callUrlData) {
if (err) {
if (err.code != 401) {
// 401 errors are already handled in hawkRequest and show an error
// message about the session.
this.props.notifications.errorL10n("unable_retrieve_url");
}
this.setState(this.getInitialState());
} else {
try {
var callUrl = new window.URL(callUrlData.callUrl);
// XXX the current server vers does not implement the callToken field
// but it exists in the API. This workaround should be removed in the future
var token = callUrlData.callToken ||
callUrl.pathname.split('/').pop();
// Now that a new URL is available, indicate it has not been shared.
this.linkExfiltrated = false;
this.setState({pending: false, copied: false,
callUrl: callUrl.href,
callUrlExpiry: callUrlData.expiresAt});
} catch(e) {
console.log(e);
this.props.notifications.errorL10n("unable_retrieve_url");
this.setState(this.getInitialState());
}
}
},
handleEmailButtonClick: function(event) {
this.handleLinkExfiltration(event);
sharedUtils.composeCallUrlEmail(this.state.callUrl);
},
handleCopyButtonClick: function(event) {
this.handleLinkExfiltration(event);
// XXX the mozLoop object should be passed as a prop, to ease testing and
// using a fake implementation in UI components showcase.
navigator.mozLoop.copyString(this.state.callUrl);
this.setState({copied: true});
},
linkExfiltrated: false,
handleLinkExfiltration: function(event) {
// Update the count of shared URLs only once per generated URL.
if (!this.linkExfiltrated) {
this.linkExfiltrated = true;
try {
navigator.mozLoop.telemetryAdd("LOOP_CLIENT_CALL_URL_SHARED", true);
} catch (err) {
console.error("Error recording telemetry", err);
}
}
// Note URL expiration every time it is shared.
if (this.state.callUrlExpiry) {
navigator.mozLoop.noteCallUrlExpiry(this.state.callUrlExpiry);
}
},
render: function() {
// XXX setting elem value from a state (in the callUrl input)
// makes it immutable ie read only but that is fine in our case.
// readOnly attr will suppress a warning regarding this issue
// from the react lib.
var cx = React.addons.classSet;
return (
<div className="generate-url">
<header id="share-link-header">{mozL10n.get("share_link_header_text")}</header>
<div className="generate-url-stack">
<input type="url" value={this.state.callUrl} readOnly="true"
onCopy={this.handleLinkExfiltration}
className={cx({"generate-url-input": true,
pending: this.state.pending,
// Used in functional testing, signals that
// call url was received from loop server
callUrl: !this.state.pending})} />
<div className={cx({"generate-url-spinner": true,
spinner: true,
busy: this.state.pending})} />
</div>
<ButtonGroup additionalClass="url-actions">
<Button additionalClass="button-email"
disabled={!this.state.callUrl}
onClick={this.handleEmailButtonClick}
caption={mozL10n.get("share_button")} />
<Button additionalClass="button-copy"
disabled={!this.state.callUrl}
onClick={this.handleCopyButtonClick}
caption={this.state.copied ? mozL10n.get("copied_url_button") :
mozL10n.get("copy_url_button")} />
</ButtonGroup>
</div>
);
}
});
/**
* FxA sign in/up link component.
*/
@ -820,9 +667,7 @@ loop.panel = (function(_, mozL10n) {
var PanelView = React.createClass({
propTypes: {
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired,
// Mostly used for UI components showcase and unit tests
callUrl: React.PropTypes.string,
userProfile: React.PropTypes.object,
// Used only for unit tests.
showTabButtons: React.PropTypes.bool,
@ -869,17 +714,13 @@ loop.panel = (function(_, mozL10n) {
}
},
_roomsEnabled: function() {
return this.props.mozLoop.getLoopPref("rooms.enabled");
},
_onStatusChanged: function() {
var profile = this.props.mozLoop.userProfile;
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
var newUid = profile ? profile.uid : null;
if (currUid != newUid) {
// On profile change (login, logout), switch back to the default tab.
this.selectTab(this._roomsEnabled() ? "rooms" : "call");
this.selectTab("rooms");
this.setState({userProfile: profile});
}
this.updateServiceErrors();
@ -902,34 +743,6 @@ loop.panel = (function(_, mozL10n) {
}
},
/**
* The rooms feature is hidden by default for now. Once it gets mainstream,
* this method can be simplified.
*/
_renderRoomsOrCallTab: function() {
if (!this._roomsEnabled()) {
return (
<Tab name="call">
<div className="content-area">
<CallUrlResult client={this.props.client}
notifications={this.props.notifications}
callUrl={this.props.callUrl} />
<ToSView />
</div>
</Tab>
);
}
return (
<Tab name="rooms">
<RoomList dispatcher={this.props.dispatcher}
store={this.props.roomStore}
userDisplayName={this._getUserDisplayName()}/>
<ToSView />
</Tab>
);
},
startForm: function(name, contact) {
this.refs[name].initForm(contact);
this.selectTab(name);
@ -986,7 +799,12 @@ loop.panel = (function(_, mozL10n) {
clearOnDocumentHidden={true} />
<TabView ref="tabView" selectedTab={this.props.selectedTab}
buttonsHidden={hideButtons}>
{this._renderRoomsOrCallTab()}
<Tab name="rooms">
<RoomList dispatcher={this.props.dispatcher}
store={this.props.roomStore}
userDisplayName={this._getUserDisplayName()}/>
<ToSView />
</Tab>
<Tab name="contacts">
<ContactsList selectTab={this.selectTab}
startForm={this.startForm}
@ -1029,7 +847,6 @@ loop.panel = (function(_, mozL10n) {
// else to ensure the L10n environment is setup correctly.
mozL10n.initialize(navigator.mozLoop);
var client = new loop.Client();
var notifications = new sharedModels.NotificationCollection();
var dispatcher = new loop.Dispatcher();
var roomStore = new loop.store.RoomStore(dispatcher, {
@ -1038,7 +855,6 @@ loop.panel = (function(_, mozL10n) {
});
React.render(<PanelView
client={client}
notifications={notifications}
roomStore={roomStore}
mozLoop={navigator.mozLoop}
@ -1057,7 +873,6 @@ loop.panel = (function(_, mozL10n) {
init: init,
AuthLink: AuthLink,
AvailabilityDropdown: AvailabilityDropdown,
CallUrlResult: CallUrlResult,
GettingStartedView: GettingStartedView,
PanelView: PanelView,
RoomEntry: RoomEntry,

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

@ -32,7 +32,6 @@
<script type="text/javascript" src="loop/shared/js/roomStates.js"></script>
<script type="text/javascript" src="loop/shared/js/fxOSActiveRoomStore.js"></script>
<script type="text/javascript" src="loop/shared/js/activeRoomStore.js"></script>
<script type="text/javascript" src="loop/js/client.js"></script>
<script type="text/javascript;version=1.8" src="loop/js/contacts.js"></script>
<script type="text/javascript" src="loop/js/panel.js"></script>
</body>

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

@ -32,7 +32,6 @@ describe("loop.Client", function() {
.returns(null)
.withArgs("hawk-session-token")
.returns(fakeToken),
noteCallUrlExpiry: sinon.spy(),
hawkRequest: sinon.stub(),
LOOP_SESSION_TYPE: {
GUEST: 1,
@ -89,140 +88,6 @@ describe("loop.Client", function() {
});
});
describe("#requestCallUrl", function() {
it("should post to /call-url/", function() {
client.requestCallUrl("foo", callback);
sinon.assert.calledOnce(hawkRequestStub);
sinon.assert.calledWithExactly(hawkRequestStub, sinon.match.number,
"/call-url/", "POST", {callerId: "foo"}, sinon.match.func);
});
it("should send a sessionType of LOOP_SESSION_TYPE.GUEST when " +
"mozLoop.userProfile returns null", function() {
mozLoop.userProfile = null;
client.requestCallUrl("foo", callback);
sinon.assert.calledOnce(hawkRequestStub);
sinon.assert.calledWithExactly(hawkRequestStub,
mozLoop.LOOP_SESSION_TYPE.GUEST, "/call-url/", "POST",
{callerId: "foo"}, sinon.match.func);
});
it("should send a sessionType of LOOP_SESSION_TYPE.FXA when " +
"mozLoop.userProfile returns an object", function () {
mozLoop.userProfile = {};
client.requestCallUrl("foo", callback);
sinon.assert.calledOnce(hawkRequestStub);
sinon.assert.calledWithExactly(hawkRequestStub,
mozLoop.LOOP_SESSION_TYPE.FXA, "/call-url/", "POST",
{callerId: "foo"}, sinon.match.func);
});
it("should call the callback with the url when the request succeeds",
function() {
var callUrlData = {
"callUrl": "fakeCallUrl",
"expiresAt": 60
};
// Sets up the hawkRequest stub to trigger the callback with no error
// and the url.
hawkRequestStub.callsArgWith(4, null, JSON.stringify(callUrlData));
client.requestCallUrl("foo", callback);
sinon.assert.calledWithExactly(callback, null, callUrlData);
});
it("should not update call url expiry when the request succeeds",
function() {
var callUrlData = {
"callUrl": "fakeCallUrl",
"expiresAt": 6000
};
// Sets up the hawkRequest stub to trigger the callback with no error
// and the url.
hawkRequestStub.callsArgWith(4, null, JSON.stringify(callUrlData));
client.requestCallUrl("foo", callback);
sinon.assert.notCalled(mozLoop.noteCallUrlExpiry);
});
it("should call mozLoop.telemetryAdd when the request succeeds",
function(done) {
var callUrlData = {
"callUrl": "fakeCallUrl",
"expiresAt": 60
};
// Sets up the hawkRequest stub to trigger the callback with no error
// and the url.
hawkRequestStub.callsArgWith(4, null,
JSON.stringify(callUrlData));
client.requestCallUrl("foo", function(err) {
expect(err).to.be.null;
sinon.assert.calledOnce(mozLoop.telemetryAdd);
sinon.assert.calledWith(mozLoop.telemetryAdd,
"LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
true);
done();
});
});
it("should send an error when the request fails", function() {
// Sets up the hawkRequest stub to trigger the callback with
// an error
hawkRequestStub.callsArgWith(4, fakeErrorRes);
client.requestCallUrl("foo", callback);
sinon.assert.calledOnce(callback);
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
return err.code == 400 && "invalid token" == err.message;
}));
});
it("should send an error if the data is not valid", function() {
// Sets up the hawkRequest stub to trigger the callback with
// an error
hawkRequestStub.callsArgWith(4, null, "{}");
client.requestCallUrl("foo", callback);
sinon.assert.calledOnce(callback);
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
return /Invalid data received/.test(err.message);
}));
});
it("should call mozLoop.telemetryAdd when the request fails",
function(done) {
// Sets up the hawkRequest stub to trigger the callback with
// an error
hawkRequestStub.callsArgWith(4, fakeErrorRes);
client.requestCallUrl("foo", function(err) {
expect(err).not.to.be.null;
sinon.assert.calledOnce(mozLoop.telemetryAdd);
sinon.assert.calledWith(mozLoop.telemetryAdd,
"LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
false);
done();
});
});
});
describe("#setupOutgoingCall", function() {
var calleeIds, callType;

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

@ -48,10 +48,6 @@ describe("loop.panel", function() {
getPluralForm: function() {
return "fakeText";
},
copyString: sandbox.stub(),
noteCallUrlExpiry: sinon.spy(),
composeEmail: sinon.spy(),
telemetryAdd: sinon.spy(),
contacts: {
getAll: function(callback) {
callback(null, []);
@ -186,69 +182,33 @@ describe("loop.panel", function() {
describe('TabView', function() {
var view, callTab, roomsTab, contactsTab;
describe("loop.rooms.enabled on", function() {
beforeEach(function() {
navigator.mozLoop.getLoopPref = function(pref) {
if (pref === "rooms.enabled" ||
pref === "gettingStarted.seen") {
return true;
}
};
beforeEach(function() {
navigator.mozLoop.getLoopPref = function(pref) {
if (pref === "gettingStarted.seen") {
return true;
}
};
view = createTestPanelView();
view = createTestPanelView();
[roomsTab, contactsTab] =
TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
});
it("should select contacts tab when clicking tab button", function() {
TestUtils.Simulate.click(
view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
expect(contactsTab.getDOMNode().classList.contains("selected"))
.to.be.true;
});
it("should select rooms tab when clicking tab button", function() {
TestUtils.Simulate.click(
view.getDOMNode().querySelector("li[data-tab-name=\"rooms\"]"));
expect(roomsTab.getDOMNode().classList.contains("selected"))
.to.be.true;
});
[roomsTab, contactsTab] =
TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
});
describe("loop.rooms.enabled off", function() {
beforeEach(function() {
navigator.mozLoop.getLoopPref = function(pref) {
if (pref === "rooms.enabled") {
return false;
} else if (pref === "gettingStarted.seen") {
return true;
}
};
it("should select contacts tab when clicking tab button", function() {
TestUtils.Simulate.click(
view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
view = createTestPanelView();
expect(contactsTab.getDOMNode().classList.contains("selected"))
.to.be.true;
});
[callTab, contactsTab] =
TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
});
it("should select rooms tab when clicking tab button", function() {
TestUtils.Simulate.click(
view.getDOMNode().querySelector("li[data-tab-name=\"rooms\"]"));
it("should select contacts tab when clicking tab button", function() {
TestUtils.Simulate.click(
view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
expect(contactsTab.getDOMNode().classList.contains("selected"))
.to.be.true;
});
it("should select call tab when clicking tab button", function() {
TestUtils.Simulate.click(
view.getDOMNode().querySelector("li[data-tab-name=\"call\"]"));
expect(callTab.getDOMNode().classList.contains("selected"))
.to.be.true;
});
expect(roomsTab.getDOMNode().classList.contains("selected"))
.to.be.true;
});
});
@ -468,284 +428,6 @@ describe("loop.panel", function() {
});
});
describe("loop.panel.CallUrlResult", function() {
var fakeClient, callUrlData, view;
beforeEach(function() {
callUrlData = {
callUrl: "http://call.invalid/fakeToken",
expiresAt: 1000
};
fakeClient = {
requestCallUrl: function(_, cb) {
cb(null, callUrlData);
}
};
sandbox.stub(notifications, "reset");
view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
});
describe("Rendering the component should generate a call URL", function() {
beforeEach(function() {
document.mozL10n.initialize({
getStrings: function(key) {
var text;
if (key === "share_email_subject4")
text = "email-subject";
else if (key === "share_email_body4")
text = "{{callUrl}}";
return JSON.stringify({textContent: text});
}
});
});
it("should make a request to requestCallUrl", function() {
sandbox.stub(fakeClient, "requestCallUrl");
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
sinon.assert.calledOnce(view.props.client.requestCallUrl);
sinon.assert.calledWithExactly(view.props.client.requestCallUrl,
sinon.match.string, sinon.match.func);
});
it("should set the call url form in a pending state", function() {
// Cancel requestCallUrl effect to keep the state pending
fakeClient.requestCallUrl = sandbox.stub();
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
expect(view.state.pending).eql(true);
});
it("should update state with the call url received", function() {
expect(view.state.pending).eql(false);
expect(view.state.callUrl).eql(callUrlData.callUrl);
});
it("should clear the pending state when a response is received",
function() {
expect(view.state.pending).eql(false);
});
it("should update CallUrlResult with the call url", function() {
var urlField = view.getDOMNode().querySelector("input[type='url']");
expect(urlField.value).eql(callUrlData.callUrl);
});
it("should have 0 pending notifications", function() {
expect(view.props.notifications.length).eql(0);
});
it("should display a share button for email", function() {
fakeClient.requestCallUrl = sandbox.stub();
var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
view.setState({pending: false, callUrl: "http://example.com"});
TestUtils.findRenderedDOMComponentWithClass(view, "button-email");
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
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() {
fakeClient.requestCallUrl = sandbox.stub();
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-copy"));
sinon.assert.calledOnce(navigator.mozLoop.copyString);
sinon.assert.calledWithExactly(navigator.mozLoop.copyString,
view.state.callUrl);
});
it("should note the call url expiry when the url is copied via button",
function() {
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-copy"));
sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
6000);
});
it("should call mozLoop.telemetryAdd when the url is copied via button",
function() {
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
// Multiple clicks should result in the URL being counted only once.
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-copy"));
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-copy"));
sinon.assert.calledOnce(navigator.mozLoop.telemetryAdd);
sinon.assert.calledWith(navigator.mozLoop.telemetryAdd,
"LOOP_CLIENT_CALL_URL_SHARED",
true);
});
it("should note the call url expiry when the url is emailed",
function() {
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
6000);
});
it("should call mozLoop.telemetryAdd when the url is emailed",
function() {
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
// Multiple clicks should result in the URL being counted only once.
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
sinon.assert.calledOnce(navigator.mozLoop.telemetryAdd);
sinon.assert.calledWith(navigator.mozLoop.telemetryAdd,
"LOOP_CLIENT_CALL_URL_SHARED",
true);
});
it("should note the call url expiry when the url is copied manually",
function() {
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
var urlField = view.getDOMNode().querySelector("input[type='url']");
TestUtils.Simulate.copy(urlField);
sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
6000);
});
it("should call mozLoop.telemetryAdd when the url is copied manually",
function() {
var view = TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
// Multiple copies should result in the URL being counted only once.
var urlField = view.getDOMNode().querySelector("input[type='url']");
TestUtils.Simulate.copy(urlField);
TestUtils.Simulate.copy(urlField);
sinon.assert.calledOnce(navigator.mozLoop.telemetryAdd);
sinon.assert.calledWith(navigator.mozLoop.telemetryAdd,
"LOOP_CLIENT_CALL_URL_SHARED",
true);
});
it("should notify the user when the operation failed", function() {
fakeClient.requestCallUrl = function(_, cb) {
cb("fake error");
};
sandbox.stub(notifications, "errorL10n");
TestUtils.renderIntoDocument(
React.createElement(loop.panel.CallUrlResult, {
notifications: notifications,
client: fakeClient
}));
sinon.assert.calledOnce(notifications.errorL10n);
sinon.assert.calledWithExactly(notifications.errorL10n,
"unable_retrieve_url");
});
});
});
describe("loop.panel.RoomEntry", function() {
var dispatcher, roomData;

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

@ -1,25 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function expiryTimePref() {
return Services.prefs.getIntPref("loop.urlsExpiryTimeSeconds");
}
function run_test()
{
setupFakeLoopServer();
Services.prefs.setIntPref("loop.urlsExpiryTimeSeconds", 0);
MozLoopService.noteCallUrlExpiry(1000);
Assert.equal(expiryTimePref(), 1000, "should be equal to set value");
MozLoopService.noteCallUrlExpiry(900);
Assert.equal(expiryTimePref(), 1000, "should remain the same value");
MozLoopService.noteCallUrlExpiry(1500);
Assert.equal(expiryTimePref(), 1500, "should be the increased value");
}

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

@ -16,8 +16,8 @@ function test_getStrings() {
// XXX This depends on the L10n values, which I'd prefer not to do, but is the
// simplest way for now.
Assert.equal(MozLoopService.getStrings("share_link_header_text"),
'{"textContent":"Share this link to invite someone to talk:"}');
Assert.equal(MozLoopService.getStrings("display_name_guest"),
'{"textContent":"Guest"}');
}
function run_test()

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

@ -9,7 +9,6 @@ skip-if = toolkit == 'gonk'
[test_looprooms.js]
[test_loopservice_directcall.js]
[test_loopservice_dnd.js]
[test_loopservice_expiry.js]
[test_loopservice_hawk_errors.js]
[test_loopservice_hawk_request.js]
[test_loopservice_loop_prefs.js]

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

@ -48,14 +48,10 @@ var fakeRooms = [
* @type {Object}
*/
navigator.mozLoop = {
roomsEnabled: false,
ensureRegistered: function() {},
getAudioBlob: function(){},
getLoopPref: function(pref) {
switch(pref) {
// Ensure UI for rooms is displayed in the showcase.
case "rooms.enabled":
return this.roomsEnabled;
// Ensure we skip FTE completely.
case "gettingStarted.seen":
return true;

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

@ -83,7 +83,6 @@
// Local mocks
var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
mockMozLoopRooms.roomsEnabled = true;
var mockContact = {
name: ["Mr Smith"],
@ -93,7 +92,6 @@
};
var mockClient = {
requestCallUrl: noop,
requestCallUrlInfo: noop
};
@ -220,33 +218,21 @@
React.createElement("p", {className: "note"},
React.createElement("strong", null, "Note:"), " 332px wide."
),
React.createElement(Example, {summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}},
React.createElement(PanelView, {client: mockClient, notifications: notifications,
callUrl: "http://invalid.example.url/",
mozLoop: navigator.mozLoop,
dispatcher: dispatcher,
roomStore: roomStore})
),
React.createElement(Example, {summary: "Call URL retrieved - authenticated", dashed: "true", style: {width: "332px"}},
React.createElement(PanelView, {client: mockClient, notifications: notifications,
callUrl: "http://invalid.example.url/",
userProfile: {email: "test@example.com"},
mozLoop: navigator.mozLoop,
dispatcher: dispatcher,
roomStore: roomStore})
),
React.createElement(Example, {summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}},
React.createElement(PanelView, {client: mockClient, notifications: notifications,
mozLoop: navigator.mozLoop,
dispatcher: dispatcher,
roomStore: roomStore})
),
React.createElement(Example, {summary: "Pending call url retrieval - authenticated", dashed: "true", style: {width: "332px"}},
React.createElement(Example, {summary: "Room list tab", dashed: "true", style: {width: "332px"}},
React.createElement(PanelView, {client: mockClient, notifications: notifications,
userProfile: {email: "test@example.com"},
mozLoop: navigator.mozLoop,
mozLoop: mockMozLoopRooms,
dispatcher: dispatcher,
roomStore: roomStore})
roomStore: roomStore,
selectedTab: "rooms"})
),
React.createElement(Example, {summary: "Contact list tab", dashed: "true", style: {width: "332px"}},
React.createElement(PanelView, {client: mockClient, notifications: notifications,
userProfile: {email: "test@example.com"},
mozLoop: mockMozLoopRooms,
dispatcher: dispatcher,
roomStore: roomStore,
selectedTab: "contacts"})
),
React.createElement(Example, {summary: "Error Notification", dashed: "true", style: {width: "332px"}},
React.createElement(PanelView, {client: mockClient, notifications: errNotifications,
@ -261,14 +247,6 @@
dispatcher: dispatcher,
roomStore: roomStore})
),
React.createElement(Example, {summary: "Room list tab", dashed: "true", style: {width: "332px"}},
React.createElement(PanelView, {client: mockClient, notifications: notifications,
userProfile: {email: "test@example.com"},
mozLoop: mockMozLoopRooms,
dispatcher: dispatcher,
roomStore: roomStore,
selectedTab: "rooms"})
),
React.createElement(Example, {summary: "Contact import success", dashed: "true", style: {width: "332px"}},
React.createElement(PanelView, {notifications: new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}]),
userProfile: {email: "test@example.com"},

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

@ -83,7 +83,6 @@
// Local mocks
var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
mockMozLoopRooms.roomsEnabled = true;
var mockContact = {
name: ["Mr Smith"],
@ -93,7 +92,6 @@
};
var mockClient = {
requestCallUrl: noop,
requestCallUrlInfo: noop
};
@ -220,33 +218,21 @@
<p className="note">
<strong>Note:</strong> 332px wide.
</p>
<Example summary="Call URL retrieved" dashed="true" style={{width: "332px"}}>
<PanelView client={mockClient} notifications={notifications}
callUrl="http://invalid.example.url/"
mozLoop={navigator.mozLoop}
dispatcher={dispatcher}
roomStore={roomStore} />
</Example>
<Example summary="Call URL retrieved - authenticated" dashed="true" style={{width: "332px"}}>
<PanelView client={mockClient} notifications={notifications}
callUrl="http://invalid.example.url/"
userProfile={{email: "test@example.com"}}
mozLoop={navigator.mozLoop}
dispatcher={dispatcher}
roomStore={roomStore} />
</Example>
<Example summary="Pending call url retrieval" dashed="true" style={{width: "332px"}}>
<PanelView client={mockClient} notifications={notifications}
mozLoop={navigator.mozLoop}
dispatcher={dispatcher}
roomStore={roomStore} />
</Example>
<Example summary="Pending call url retrieval - authenticated" dashed="true" style={{width: "332px"}}>
<Example summary="Room list tab" dashed="true" style={{width: "332px"}}>
<PanelView client={mockClient} notifications={notifications}
userProfile={{email: "test@example.com"}}
mozLoop={navigator.mozLoop}
mozLoop={mockMozLoopRooms}
dispatcher={dispatcher}
roomStore={roomStore} />
roomStore={roomStore}
selectedTab="rooms" />
</Example>
<Example summary="Contact list tab" dashed="true" style={{width: "332px"}}>
<PanelView client={mockClient} notifications={notifications}
userProfile={{email: "test@example.com"}}
mozLoop={mockMozLoopRooms}
dispatcher={dispatcher}
roomStore={roomStore}
selectedTab="contacts" />
</Example>
<Example summary="Error Notification" dashed="true" style={{width: "332px"}}>
<PanelView client={mockClient} notifications={errNotifications}
@ -261,14 +247,6 @@
dispatcher={dispatcher}
roomStore={roomStore} />
</Example>
<Example summary="Room list tab" dashed="true" style={{width: "332px"}}>
<PanelView client={mockClient} notifications={notifications}
userProfile={{email: "test@example.com"}}
mozLoop={mockMozLoopRooms}
dispatcher={dispatcher}
roomStore={roomStore}
selectedTab="rooms" />
</Example>
<Example summary="Contact import success" dashed="true" style={{width: "332px"}}>
<PanelView notifications={new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}])}
userProfile={{email: "test@example.com"}}

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

@ -13,30 +13,8 @@ clientShortname2=Firefox Hello
first_time_experience_title={{clientShortname}} — Join the conversation
first_time_experience_button_label=Get Started
share_link_header_text=Share this link to invite someone to talk:
invite_header_text=Invite someone to join you.
## LOCALIZATION NOTE(invitee_name_label): Displayed when obtaining a url.
## See https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#precall-firstrun
## Click the label icon at the end of the url field.
invitee_name_label=Who are you inviting?
## LOCALIZATION NOTE(invitee_expire_days_label): Allows the user to adjust
## the expiry time. Click the label icon at the end of the url field to see where
## this is:
## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#precall-firstrun
## Semicolon-separated list of plural forms. See:
## http://developer.mozilla.org/en/docs/Localization_and_Plurals
## In this item, don't translate the part between {{..}}
invitee_expire_days_label=Invitation will expire in {{expiry_time}} day;Invitation will expire in {{expiry_time}} days
## LOCALIZATION NOTE(invitee_expire_hours_label): Allows the user to adjust
## the expiry time. Click the label icon are the end of the url field to see where
## this is:
## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#precall-firstrun
## Semicolon-separated list of plural forms. See:
## http://developer.mozilla.org/en/docs/Localization_and_Plurals
## In this item, don't translate the part between {{..}}
invitee_expire_hours_label=Invitation will expire in {{expiry_time}} hour;Invitation will expire in {{expiry_time}} hours
# Status text
display_name_guest=Guest
display_name_dnd_status=Do Not Disturb
@ -66,9 +44,7 @@ share_email_subject4={{clientShortname}} — Join the conversation
## LOCALIZATION NOTE (share_email_body4): In this item, don't translate the
## part between {{..}} and leave the \r\n\r\n part alone
share_email_body4=Hello!\r\n\r\nJoin me for a video conversation using {{clientShortname}}:\r\n\r\nYou don't have to download or install anything. Just copy and paste this URL into your browser:\r\n\r\n{{callUrl}}\r\n\r\nIf you want, you can also learn more about {{clientShortname}} at {{learnMoreUrl}}\r\n\r\nTalk to you soon!
share_button=Email
share_button2=Email Link
copy_url_button=Copy
copy_url_button2=Copy Link
copied_url_button=Copied!