--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -10,16 +10,17 @@
var loop = loop || {};
loop.conversation = (function(mozL10n) {
"use strict";
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
var sharedModels = loop.shared.models;
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
+ var CallIdentifierView = loop.conversationViews.CallIdentifierView;
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
mixins: [sharedMixins.DropdownMenuMixin],
propTypes: {
model: React.PropTypes.object.isRequired,
video: React.PropTypes.bool.isRequired
},
@@ -89,19 +90,24 @@ loop.conversation = (function(mozL10n) {
render: function() {
/* jshint ignore:start */
var dropdownMenuClassesDecline = React.addons.classSet({
"native-dropdown-menu": true,
"conversation-window-dropdown": true,
"visually-hidden": !this.state.showMenu
});
+
return (
React.DOM.div({className: "call-window"},
- React.DOM.h2(null, mozL10n.get("incoming_call_title2")),
+ CallIdentifierView({video: this.props.video,
+ peerIdentifier: this.props.model.getCallIdentifier(),
+ urlCreationDate: this.props.model.get("urlCreationDate"),
+ showIcons: true}),
+
React.DOM.div({className: "btn-group call-action-group"},
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
React.DOM.div({className: "btn-chevron-menu-group"},
React.DOM.div({className: "btn-group-chevron"},
React.DOM.div({className: "btn-group"},
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -10,16 +10,17 @@
var loop = loop || {};
loop.conversation = (function(mozL10n) {
"use strict";
var sharedViews = loop.shared.views;
var sharedMixins = loop.shared.mixins;
var sharedModels = loop.shared.models;
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
+ var CallIdentifierView = loop.conversationViews.CallIdentifierView;
var IncomingCallView = React.createClass({
mixins: [sharedMixins.DropdownMenuMixin],
propTypes: {
model: React.PropTypes.object.isRequired,
video: React.PropTypes.bool.isRequired
},
@@ -89,19 +90,24 @@ loop.conversation = (function(mozL10n) {
render: function() {
/* jshint ignore:start */
var dropdownMenuClassesDecline = React.addons.classSet({
"native-dropdown-menu": true,
"conversation-window-dropdown": true,
"visually-hidden": !this.state.showMenu
});
+
return (
<div className="call-window">
- <h2>{mozL10n.get("incoming_call_title2")}</h2>
+ <CallIdentifierView video={this.props.video}
+ peerIdentifier={this.props.model.getCallIdentifier()}
+ urlCreationDate={this.props.model.get("urlCreationDate")}
+ showIcons={true} />
+
<div className="btn-group call-action-group">
<div className="fx-embedded-call-button-spacer"></div>
<div className="btn-chevron-menu-group">
<div className="btn-group-chevron">
<div className="btn-group">
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -10,16 +10,83 @@ var loop = loop || {};
loop.conversationViews = (function(mozL10n) {
var CALL_STATES = loop.store.CALL_STATES;
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
var sharedActions = loop.shared.actions;
var sharedViews = loop.shared.views;
/**
+ * Displays information about the call
+ * Caller avatar, name & conversation creation date
+ */
+ var CallIdentifierView = React.createClass({displayName: 'CallIdentifierView',
+ propTypes: {
+ peerIdentifier: React.PropTypes.string,
+ showIcons: React.PropTypes.bool.isRequired,
+ urlCreationDate: React.PropTypes.string,
+ video: React.PropTypes.bool
+ },
+
+ getDefaultProps: function() {
+ return {
+ peerIdentifier: "",
+ showLinkDetail: true,
+ urlCreationDate: "",
+ video: true
+ };
+ },
+
+ getInitialState: function() {
+ return {timestamp: 0};
+ },
+
+ /**
+ * Gets and formats the incoming call creation date
+ */
+ formatCreationDate: function() {
+ if (!this.props.urlCreationDate) {
+ return "";
+ }
+
+ var timestamp = this.props.urlCreationDate;
+ return "(" + loop.shared.utils.formatDate(timestamp) + ")";
+ },
+
+ render: function() {
+ var iconVideoClasses = React.addons.classSet({
+ "fx-embedded-tiny-video-icon": true,
+ "muted": !this.props.video
+ });
+ var callDetailClasses = React.addons.classSet({
+ "fx-embedded-call-detail": true,
+ "hide": !this.props.showIcons
+ });
+
+ return (
+ React.DOM.div({className: "fx-embedded-call-identifier"},
+ React.DOM.div({className: "fx-embedded-call-identifier-avatar fx-embedded-call-identifier-item"}),
+ React.DOM.div({className: "fx-embedded-call-identifier-info fx-embedded-call-identifier-item"},
+ React.DOM.div({className: "fx-embedded-call-identifier-text overflow-text-ellipsis font-bold"},
+ this.props.peerIdentifier
+ ),
+ React.DOM.div({className: callDetailClasses},
+ React.DOM.span({className: "fx-embedded-tiny-audio-icon"}),
+ React.DOM.span({className: iconVideoClasses}),
+ React.DOM.span({className: "fx-embedded-conversation-timestamp"},
+ this.formatCreationDate()
+ )
+ )
+ )
+ )
+ );
+ }
+ });
+
+ /**
* Displays details of the incoming/outgoing conversation
* (name, link, audio/video type etc).
*
* Allows the view to be extended with different buttons and progress
* via children properties.
*/
var ConversationDetailView = React.createClass({displayName: 'ConversationDetailView',
propTypes: {
@@ -46,17 +113,19 @@ loop.conversationViews = (function(mozL1
} else {
contactName = this._getPreferredEmail(this.props.contact).value;
}
document.title = contactName;
return (
React.DOM.div({className: "call-window"},
- React.DOM.h2(null, contactName),
+ CallIdentifierView({
+ peerIdentifier: contactName,
+ showIcons: false}),
React.DOM.div(null, this.props.children)
)
);
}
});
/**
* View for pending conversations. Displays a cancel button and appropriate
@@ -377,15 +446,16 @@ loop.conversationViews = (function(mozL1
))
}
}
},
});
return {
PendingConversationView: PendingConversationView,
+ CallIdentifierView: CallIdentifierView,
ConversationDetailView: ConversationDetailView,
CallFailedView: CallFailedView,
OngoingConversationView: OngoingConversationView,
OutgoingConversationView: OutgoingConversationView
};
})(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -10,16 +10,83 @@ var loop = loop || {};
loop.conversationViews = (function(mozL10n) {
var CALL_STATES = loop.store.CALL_STATES;
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
var sharedActions = loop.shared.actions;
var sharedViews = loop.shared.views;
/**
+ * Displays information about the call
+ * Caller avatar, name & conversation creation date
+ */
+ var CallIdentifierView = React.createClass({
+ propTypes: {
+ peerIdentifier: React.PropTypes.string,
+ showIcons: React.PropTypes.bool.isRequired,
+ urlCreationDate: React.PropTypes.string,
+ video: React.PropTypes.bool
+ },
+
+ getDefaultProps: function() {
+ return {
+ peerIdentifier: "",
+ showLinkDetail: true,
+ urlCreationDate: "",
+ video: true
+ };
+ },
+
+ getInitialState: function() {
+ return {timestamp: 0};
+ },
+
+ /**
+ * Gets and formats the incoming call creation date
+ */
+ formatCreationDate: function() {
+ if (!this.props.urlCreationDate) {
+ return "";
+ }
+
+ var timestamp = this.props.urlCreationDate;
+ return "(" + loop.shared.utils.formatDate(timestamp) + ")";
+ },
+
+ render: function() {
+ var iconVideoClasses = React.addons.classSet({
+ "fx-embedded-tiny-video-icon": true,
+ "muted": !this.props.video
+ });
+ var callDetailClasses = React.addons.classSet({
+ "fx-embedded-call-detail": true,
+ "hide": !this.props.showIcons
+ });
+
+ return (
+ <div className="fx-embedded-call-identifier">
+ <div className="fx-embedded-call-identifier-avatar fx-embedded-call-identifier-item"/>
+ <div className="fx-embedded-call-identifier-info fx-embedded-call-identifier-item">
+ <div className="fx-embedded-call-identifier-text overflow-text-ellipsis>
+ {this.props.peerIdentifier}
+ </div>
+ <div className={callDetailClasses}>
+ <span className="fx-embedded-tiny-audio-icon"></span>
+ <span className={iconVideoClasses}></span>
+ <span className="fx-embedded-conversation-timestamp">
+ {this.formatCreationDate()}
+ </span>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ });
+
+ /**
* Displays details of the incoming/outgoing conversation
* (name, link, audio/video type etc).
*
* Allows the view to be extended with different buttons and progress
* via children properties.
*/
var ConversationDetailView = React.createClass({
propTypes: {
@@ -46,17 +113,19 @@ loop.conversationViews = (function(mozL1
} else {
contactName = this._getPreferredEmail(this.props.contact).value;
}
document.title = contactName;
return (
<div className="call-window">
- <h2>{contactName}</h2>
+ <CallIdentifierView
+ peerIdentifier={contactName}
+ showIcons={false} />
<div>{this.props.children}</div>
</div>
);
}
});
/**
* View for pending conversations. Displays a cancel button and appropriate
@@ -377,15 +446,16 @@ loop.conversationViews = (function(mozL1
/>)
}
}
},
});
return {
PendingConversationView: PendingConversationView,
+ CallIdentifierView: CallIdentifierView,
ConversationDetailView: ConversationDetailView,
CallFailedView: CallFailedView,
OngoingConversationView: OngoingConversationView,
OutgoingConversationView: OutgoingConversationView
};
})(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -303,41 +303,34 @@ loop.panel = (function(_, mozL10n) {
/**
* Provided by DocumentVisibilityMixin. Schedules retrieval of a new call
* URL everytime the panel is reopened.
*/
onDocumentVisible: function() {
this._fetchCallUrl();
},
- /**
- * Returns a random 5 character string used to identify
- * the conversation.
- * XXX this will go away once the backend changes
- */
- conversationIdentifier: function() {
- return Math.random().toString(36).substring(5);
- },
-
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});
- this.props.client.requestCallUrl(this.conversationIdentifier(),
+ // 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.
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -303,41 +303,34 @@ loop.panel = (function(_, mozL10n) {
/**
* Provided by DocumentVisibilityMixin. Schedules retrieval of a new call
* URL everytime the panel is reopened.
*/
onDocumentVisible: function() {
this._fetchCallUrl();
},
- /**
- * Returns a random 5 character string used to identify
- * the conversation.
- * XXX this will go away once the backend changes
- */
- conversationIdentifier: function() {
- return Math.random().toString(36).substring(5);
- },
-
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});
- this.props.client.requestCallUrl(this.conversationIdentifier(),
+ // 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.
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -89,22 +89,24 @@
vertical-align: top;
width: .8rem;
height: .8rem;
background-repeat: no-repeat;
cursor: pointer;
}
.fx-embedded-btn-icon-video,
-.fx-embedded-btn-video-small {
+.fx-embedded-btn-video-small,
+.fx-embedded-tiny-video-icon {
background-image: url("../img/video-inverse-14x14.png");
}
.fx-embedded-btn-icon-audio,
-.fx-embedded-btn-audio-small {
+.fx-embedded-btn-audio-small,
+.fx-embedded-tiny-audio-icon {
background-image: url("../img/audio-inverse-14x14.png");
}
.fx-embedded-btn-audio-small,
.fx-embedded-btn-video-small {
width: 26px;
height: 26px;
border-left: 1px solid rgba(255,255,255,.4);
@@ -479,16 +481,84 @@
background-size: contain;
background-position: center;
}
.fx-embedded .media.nested {
min-height: 200px;
}
+.fx-embedded-call-identifier {
+ display: inline;
+ width: 100%;
+ padding: 1.2em;
+}
+
+.fx-embedded-call-identifier-item {
+ height: 50px;
+}
+
+.fx-embedded-call-identifier-avatar {
+ max-width: 50px;
+ min-width: 50px;
+ background: #ccc;
+ border-radius: 50%;
+ background-image: url("../img/audio-call-avatar.svg");
+ background-repeat: no-repeat;
+ background-color: #4ba6e7;
+ background-size: contain;
+ overflow: hidden;
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
+ float: left;
+ -moz-margin-end: 1em;
+}
+
+.fx-embedded-call-identifier-text {
+ font-weight: bold;
+}
+
+.fx-embedded-call-identifier-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ -moz-margin-start: 1em;
+}
+
+.fx-embedded-conversation-timestamp {
+ font-size: .6rem;
+ line-height: 17px;
+ display: inline-block;
+ vertical-align: top;
+}
+
+.fx-embedded-call-detail {
+ padding-top: 1.2em;
+}
+
+.fx-embedded-tiny-video-icon {
+ margin: 0 0.8em;
+}
+
+.fx-embedded-tiny-audio-icon,
+.fx-embedded-tiny-video-icon {
+ width: 18px;
+ height: 18px;
+ background-size: 12px 12px;
+ background-color: #4ba6e7;
+ display: inline-block;
+ background-repeat: no-repeat;
+ background-position: center;
+ border-radius: 50%;
+}
+
+ .fx-embedded-tiny-video-icon.muted {
+ background-color: rgba(0,0,0,.2)
+ }
+
@media screen and (min-width:640px) {
/* Force full height on all parents up to the video elements
* this way we can ensure the aspect ratio and use height 100%
* on the video element
* */
html, body, #main,
.video-layout-wrapper,
--- a/browser/components/loop/content/shared/js/models.js
+++ b/browser/components/loop/content/shared/js/models.js
@@ -27,16 +27,17 @@ loop.shared.models = (function(l10n) {
websocketToken: undefined, // The token to use for websocket auth, this is
// stored as a hex string which is what the server
// requires.
callType: undefined, // The type of incoming call selected by
// other peer ("audio" or "audio-video")
selectedCallType: "audio-video", // The selected type for the call that was
// initiated ("audio" or "audio-video")
callToken: undefined, // Incoming call token.
+ callUrl: undefined, // Incoming call url
// Used for blocking a call url
subscribedStream: false, // Used to indicate that a stream has been
// subscribed to
publishedStream: false // Used to indicate that a stream has been
// published
},
/**
@@ -137,25 +138,28 @@ loop.shared.models = (function(l10n) {
/**
* Sets session information about the incoming call.
*
* @param {Object} sessionData Conversation session information.
*/
setIncomingSessionData: function(sessionData) {
// Explicit property assignment to prevent later "surprises"
this.set({
- sessionId: sessionData.sessionId,
- sessionToken: sessionData.sessionToken,
- sessionType: sessionData.sessionType,
- apiKey: sessionData.apiKey,
- callId: sessionData.callId,
- progressURL: sessionData.progressURL,
- websocketToken: sessionData.websocketToken.toString(16),
- callType: sessionData.callType || "audio-video",
- callToken: sessionData.callToken
+ sessionId: sessionData.sessionId,
+ sessionToken: sessionData.sessionToken,
+ sessionType: sessionData.sessionType,
+ apiKey: sessionData.apiKey,
+ callId: sessionData.callId,
+ callerId: sessionData.callerId,
+ urlCreationDate: sessionData.urlCreationDate,
+ progressURL: sessionData.progressURL,
+ websocketToken: sessionData.websocketToken.toString(16),
+ callType: sessionData.callType || "audio-video",
+ callToken: sessionData.callToken,
+ callUrl: sessionData.callUrl
});
},
/**
* Starts a SDK session and subscribe to call events.
*/
startSession: function() {
if (!this.isSessionReady()) {
@@ -194,16 +198,33 @@ loop.shared.models = (function(l10n) {
}
if (callType === "outgoing") {
return this.get("selectedCallType") === "audio-video";
}
return undefined;
},
/**
+ * Used to remove the scheme from a url.
+ */
+ _removeScheme: function(url) {
+ if (!url) {
+ return "";
+ }
+ return url.replace(/^https?:\/\//, "");
+ },
+
+ /**
+ * Returns a conversation identifier for the incoming call view
+ */
+ getCallIdentifier: function() {
+ return this.get("callerId") || this._removeScheme(this.get("callUrl"));
+ },
+
+ /**
* Publishes a local stream.
*
* @param {Publisher} publisher The publisher object to publish
* to the session.
*/
publish: function(publisher) {
this.session.publish(publisher);
this.set("publishedStream", true);
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -13,16 +13,28 @@ loop.shared.utils = (function() {
* Call types used for determining if a call is audio/video or audio-only.
*/
var CALL_TYPES = {
AUDIO_VIDEO: "audio-video",
AUDIO_ONLY: "audio"
};
/**
+ * Format a given date into an l10n-friendly string.
+ *
+ * @param {Integer} The timestamp in seconds to format.
+ * @return {String} The formatted string.
+ */
+ function formatDate(timestamp) {
+ var date = (new Date(timestamp * 1000));
+ var options = {year: "numeric", month: "long", day: "numeric"};
+ return date.toLocaleDateString(navigator.language, options);
+ }
+
+ /**
* Used for adding different styles to the panel
* @returns {String} Corresponds to the client platform
* */
function getTargetPlatform() {
var platform="unknown_platform";
if (navigator.platform.indexOf("Win") !== -1) {
platform = "windows";
@@ -82,12 +94,13 @@ loop.shared.utils = (function() {
locationHash: function() {
return window.location.hash;
}
};
return {
CALL_TYPES: CALL_TYPES,
Helper: Helper,
+ formatDate: formatDate,
getTargetPlatform: getTargetPlatform,
getBoolPreference: getBoolPreference
};
})();
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -441,20 +441,19 @@ loop.webapp = (function($, _, OT, mozL10
this.setState({disableCallButton: true});
}.bind(this);
},
_setConversationTimestamp: function(err, callUrlInfo) {
if (err) {
this.props.notifications.errorL10n("unable_retrieve_call_info");
} else {
- var date = (new Date(callUrlInfo.urlCreationDate * 1000));
- var options = {year: "numeric", month: "long", day: "numeric"};
- var timestamp = date.toLocaleDateString(navigator.language, options);
- this.setState({urlCreationDateString: timestamp});
+ this.setState({
+ urlCreationDateString: sharedUtils.formatDate(callUrlInfo.urlCreationDate)
+ });
}
},
render: function() {
var tosLinkName = mozL10n.get("terms_of_use_link_text");
var privacyNoticeName = mozL10n.get("privacy_notice_link_text");
var tosHTML = mozL10n.get("legal_text_and_links", {
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -441,20 +441,19 @@ loop.webapp = (function($, _, OT, mozL10
this.setState({disableCallButton: true});
}.bind(this);
},
_setConversationTimestamp: function(err, callUrlInfo) {
if (err) {
this.props.notifications.errorL10n("unable_retrieve_call_info");
} else {
- var date = (new Date(callUrlInfo.urlCreationDate * 1000));
- var options = {year: "numeric", month: "long", day: "numeric"};
- var timestamp = date.toLocaleDateString(navigator.language, options);
- this.setState({urlCreationDateString: timestamp});
+ this.setState({
+ urlCreationDateString: sharedUtils.formatDate(callUrlInfo.urlCreationDate)
+ });
}
},
render: function() {
var tosLinkName = mozL10n.get("terms_of_use_link_text");
var privacyNoticeName = mozL10n.get("privacy_notice_link_text");
var tosHTML = mozL10n.get("legal_text_and_links", {
--- a/browser/components/loop/test/desktop-local/conversationViews_test.js
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -30,45 +30,97 @@ describe("loop.conversationViews", funct
});
afterEach(function() {
document.title = oldTitle;
view = undefined;
sandbox.restore();
});
+ describe("CallIdentifierView", function() {
+ function mountTestComponent(props) {
+ return TestUtils.renderIntoDocument(
+ loop.conversationViews.CallIdentifierView(props));
+ }
+
+ it("should set display the peer identifer", function() {
+ view = mountTestComponent({
+ showIcons: false,
+ peerIdentifier: "mrssmith"
+ });
+
+ expect(TestUtils.findRenderedDOMComponentWithClass(
+ view, "fx-embedded-call-identifier-text").props.children).eql("mrssmith");
+ });
+
+ it("should not display the icons if showIcons is false", function() {
+ view = mountTestComponent({
+ showIcons: false,
+ peerIdentifier: "mrssmith"
+ });
+
+ expect(TestUtils.findRenderedDOMComponentWithClass(
+ view, "fx-embedded-call-detail").props.className).to.contain("hide");
+ });
+
+ it("should display the icons if showIcons is true", function() {
+ view = mountTestComponent({
+ showIcons: true,
+ peerIdentifier: "mrssmith"
+ });
+
+ expect(TestUtils.findRenderedDOMComponentWithClass(
+ view, "fx-embedded-call-detail").props.className).to.not.contain("hide");
+ });
+
+ it("should display the url timestamp", function() {
+ sandbox.stub(loop.shared.utils, "formatDate").returns(("October 9, 2014"));
+
+ view = mountTestComponent({
+ showIcons: true,
+ peerIdentifier: "mrssmith",
+ urlCreationDate: (new Date() / 1000).toString()
+ });
+
+ expect(TestUtils.findRenderedDOMComponentWithClass(
+ view, "fx-embedded-conversation-timestamp").props.children).eql("(October 9, 2014)");
+ });
+
+ it("should show video as muted if video is false", function() {
+ view = mountTestComponent({
+ showIcons: true,
+ peerIdentifier: "mrssmith",
+ video: false
+ });
+
+ expect(TestUtils.findRenderedDOMComponentWithClass(
+ view, "fx-embedded-tiny-video-icon").props.className).to.contain("muted");
+ });
+ });
+
describe("ConversationDetailView", function() {
function mountTestComponent(props) {
return TestUtils.renderIntoDocument(
loop.conversationViews.ConversationDetailView(props));
}
it("should set the document title to the calledId", function() {
mountTestComponent({contact: contact});
expect(document.title).eql("mrsmith");
});
- it("should set display the calledId", function() {
- 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});
+ mountTestComponent({contact: contact});
- expect(TestUtils.findRenderedDOMComponentWithTag(
- view, "h2").props.children).eql("fakeEmail");
- }
- );
+ expect(document.title).eql("fakeEmail");
+ });
});
describe("PendingConversationView", function() {
function mountTestComponent(props) {
return TestUtils.renderIntoDocument(
loop.conversationViews.PendingConversationView(props));
}
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -691,17 +691,19 @@ describe("loop.conversation", function()
});
});
});
describe("IncomingCallView", function() {
var view, model;
beforeEach(function() {
- var Model = Backbone.Model.extend({});
+ var Model = Backbone.Model.extend({
+ getCallIdentifier: function() {return "fakeId";}
+ });
model = new Model();
sandbox.spy(model, "trigger");
sandbox.stub(model, "set");
view = TestUtils.renderIntoDocument(loop.conversation.IncomingCallView({
model: model,
video: true
}));
--- a/browser/components/loop/test/shared/models_test.js
+++ b/browser/components/loop/test/shared/models_test.js
@@ -22,17 +22,19 @@ describe("loop.shared.models", function(
requests.push(xhr);
};
fakeSessionData = {
sessionId: "sessionId",
sessionToken: "sessionToken",
apiKey: "apiKey",
callType: "callType",
websocketToken: 123,
- callToken: "callToken"
+ callToken: "callToken",
+ callUrl: "http://invalid/callToken",
+ callerId: "mrssmith"
};
fakeSession = _.extend({
connect: function () {},
endSession: sandbox.stub(),
set: sandbox.stub(),
disconnect: sandbox.spy(),
unpublish: sandbox.spy()
}, Backbone.Events);
@@ -355,16 +357,38 @@ describe("loop.shared.models", function(
});
it("should return true for outgoing callType", function() {
model.set("selectedCallType", "audio-video");
expect(model.hasVideoStream("outgoing")).to.eql(true);
});
});
+
+ describe("#getCallIdentifier", function() {
+ var model;
+
+ beforeEach(function() {
+ model = new sharedModels.ConversationModel(fakeSessionData, {
+ sdk: fakeSDK
+ });
+ model.startSession();
+ });
+
+ it("should return the callerId", function() {
+ expect(model.getCallIdentifier()).to.eql("mrssmith");
+ });
+
+ it("should return the shorted callUrl if the callerId does not exist",
+ function() {
+ model.set({"callerId": ""});
+
+ expect(model.getCallIdentifier()).to.eql("invalid/callToken");
+ });
+ });
});
});
describe("NotificationCollection", function() {
var collection, notifData, testNotif;
beforeEach(function() {
collection = new sharedModels.NotificationCollection();
--- a/browser/components/loop/test/shared/utils_test.js
+++ b/browser/components/loop/test/shared/utils_test.js
@@ -83,16 +83,36 @@ describe("loop.shared.utils", function()
it("shouldn't detect FirefoxOS on non mobile platform", function() {
expect(helper.isFirefoxOS("whatever")).eql(false);
});
});
});
});
+ describe("#formatDate", function() {
+ beforeEach(function() {
+ sandbox.stub(Date.prototype, "toLocaleDateString").returns("fake result");
+ });
+
+ it("should call toLocaleDateString with arguments", function() {
+ sharedUtils.formatDate(1000);
+
+ sinon.assert.calledOnce(Date.prototype.toLocaleDateString);
+ sinon.assert.calledWithExactly(Date.prototype.toLocaleDateString,
+ navigator.language,
+ {year: "numeric", month: "long", day: "numeric"}
+ );
+ });
+
+ it("should return the formatted string", function() {
+ expect(sharedUtils.formatDate(1000)).eql("fake result");
+ });
+ });
+
describe("#getBoolPreference", function() {
afterEach(function() {
navigator.mozLoop = undefined;
localStorage.removeItem("test.true");
});
describe("mozLoop set", function() {
beforeEach(function() {
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -73,17 +73,20 @@
var mockClient = {
requestCallUrl: noop,
requestCallUrlInfo: noop
};
var mockSDK = {};
- var mockConversationModel = new loop.shared.models.ConversationModel({}, {
+ var mockConversationModel = new loop.shared.models.ConversationModel({
+ callerId: "Mrs Jones",
+ urlCreationDate: (new Date() / 1000).toString()
+ }, {
sdk: mockSDK
});
mockConversationModel.startSession = noop;
var mockWebSocket = new loop.CallConnectionWebSocket({
url: "fake",
callId: "fakeId",
websocketToken: "fakeToken"
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -73,17 +73,20 @@
var mockClient = {
requestCallUrl: noop,
requestCallUrlInfo: noop
};
var mockSDK = {};
- var mockConversationModel = new loop.shared.models.ConversationModel({}, {
+ var mockConversationModel = new loop.shared.models.ConversationModel({
+ callerId: "Mrs Jones",
+ urlCreationDate: (new Date() / 1000).toString()
+ }, {
sdk: mockSDK
});
mockConversationModel.startSession = noop;
var mockWebSocket = new loop.CallConnectionWebSocket({
url: "fake",
callId: "fakeId",
websocketToken: "fakeToken"