Bug 1171940: update/ fix tests that my patches above have broken and lower the total amount of warnings generated by React in the console. r=Standard8
authorMike de Boer <mdeboer@mozilla.com>
Fri, 03 Jul 2015 19:00:30 +0200
changeset 275700 cbe3ff5e3877fc4188c9e85fd0edd1cc7122eebb
parent 275699 4ea7fbda92cc049f7f455c6ce20d1419c52803a8
child 275701 ea4206694632cd2077dc559fa1e9f36fd15e53dc
push id3220
push userbzhao@mozilla.com
push dateMon, 06 Jul 2015 03:31:09 +0000
reviewersStandard8
bugs1171940
milestone42.0a1
Bug 1171940: update/ fix tests that my patches above have broken and lower the total amount of warnings generated by React in the console. r=Standard8
browser/components/loop/content/js/contacts.js
browser/components/loop/content/js/contacts.jsx
browser/components/loop/content/js/conversationViews.js
browser/components/loop/content/js/conversationViews.jsx
browser/components/loop/content/js/roomViews.js
browser/components/loop/content/js/roomViews.jsx
browser/components/loop/content/shared/js/views.js
browser/components/loop/content/shared/js/views.jsx
browser/components/loop/standalone/content/js/standaloneRoomViews.js
browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
browser/components/loop/test/desktop-local/contacts_test.js
browser/components/loop/test/desktop-local/conversation_test.js
browser/components/loop/test/desktop-local/index.html
browser/components/loop/test/desktop-local/panel_test.js
browser/components/loop/test/desktop-local/roomViews_test.js
browser/components/loop/test/shared/index.html
browser/components/loop/test/shared/textChatView_test.js
browser/components/loop/test/shared/views_test.js
browser/components/loop/test/standalone/index.html
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -124,17 +124,19 @@ loop.contacts = (function(_, mozL10n) {
         "learn_more": React.renderToStaticMarkup(
           React.createElement("a", {href: privacyUrl, target: "_blank"}, 
             mozL10n.get("gravatars_promo_message_learnmore")
           )
         )
       });
       return (
         React.createElement("div", {className: "contacts-gravatar-promo"}, 
-          React.createElement(Button, {additionalClass: "button-close", onClick: this.handleCloseButtonClick}), 
+          React.createElement(Button, {additionalClass: "button-close", 
+                  caption: "", 
+                  onClick: this.handleCloseButtonClick}), 
           React.createElement("p", {dangerouslySetInnerHTML: {__html: message}, 
              onClick: this.handleLinkClick}), 
           React.createElement(ButtonGroup, null, 
             React.createElement(Button, {caption: mozL10n.get("gravatars_promo_button_nothanks"), 
                     onClick: this.handleCloseButtonClick}), 
             React.createElement(Button, {additionalClass: "button-accept", 
                     caption: mozL10n.get("gravatars_promo_button_use"), 
                     onClick: this.handleUseButtonClick})
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -124,17 +124,19 @@ loop.contacts = (function(_, mozL10n) {
         "learn_more": React.renderToStaticMarkup(
           <a href={privacyUrl} target="_blank">
             {mozL10n.get("gravatars_promo_message_learnmore")}
           </a>
         )
       });
       return (
         <div className="contacts-gravatar-promo">
-          <Button additionalClass="button-close" onClick={this.handleCloseButtonClick}/>
+          <Button additionalClass="button-close"
+                  caption=""
+                  onClick={this.handleCloseButtonClick} />
           <p dangerouslySetInnerHTML={{__html: message}}
              onClick={this.handleLinkClick}></p>
           <ButtonGroup>
             <Button caption={mozL10n.get("gravatars_promo_button_nothanks")}
                     onClick={this.handleCloseButtonClick}/>
             <Button additionalClass="button-accept"
                     caption={mozL10n.get("gravatars_promo_button_use")}
                     onClick={this.handleUseButtonClick}/>
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -654,23 +654,25 @@ loop.conversationViews = (function(mozL1
 
       return (
         React.createElement("div", {className: "video-layout-wrapper"}, 
           React.createElement("div", {className: "conversation"}, 
             React.createElement("div", {className: "media nested"}, 
               React.createElement("div", {className: "video_wrapper remote_wrapper"}, 
                 React.createElement("div", {className: "video_inner remote focus-stream"}, 
                   React.createElement(sharedViews.MediaView, {displayAvatar: !this.shouldRenderRemoteVideo(), 
+                    isLoading: false, 
                     mediaType: "remote", 
                     posterUrl: this.props.remotePosterUrl, 
                     srcVideoObject: this.state.remoteSrcVideoObject})
                 )
               ), 
               React.createElement("div", {className: localStreamClasses}, 
                 React.createElement(sharedViews.MediaView, {displayAvatar: !this.props.video.enabled, 
+                  isLoading: false, 
                   mediaType: "local", 
                   posterUrl: this.props.localPosterUrl, 
                   srcVideoObject: this.state.localSrcVideoObject})
               )
             ), 
             React.createElement(loop.shared.views.ConversationToolbar, {
               audio: this.props.audio, 
               dispatcher: this.props.dispatcher, 
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -654,23 +654,25 @@ loop.conversationViews = (function(mozL1
 
       return (
         <div className="video-layout-wrapper">
           <div className="conversation">
             <div className="media nested">
               <div className="video_wrapper remote_wrapper">
                 <div className="video_inner remote focus-stream">
                   <sharedViews.MediaView displayAvatar={!this.shouldRenderRemoteVideo()}
+                    isLoading={false}
                     mediaType="remote"
                     posterUrl={this.props.remotePosterUrl}
                     srcVideoObject={this.state.remoteSrcVideoObject} />
                 </div>
               </div>
               <div className={localStreamClasses}>
                 <sharedViews.MediaView displayAvatar={!this.props.video.enabled}
+                  isLoading={false}
                   mediaType="local"
                   posterUrl={this.props.localPosterUrl}
                   srcVideoObject={this.state.localSrcVideoObject} />
               </div>
             </div>
             <loop.shared.views.ConversationToolbar
               audio={this.props.audio}
               dispatcher={this.props.dispatcher}
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -280,18 +280,16 @@ loop.roomViews = (function(mozL10n) {
       );
     }
   });
 
   var DesktopRoomEditContextView = React.createClass({displayName: "DesktopRoomEditContextView",
     mixins: [React.addons.LinkedStateMixin],
 
     propTypes: {
-      // Only used for tests.
-      availableContext: React.PropTypes.object,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       error: React.PropTypes.object,
       mozLoop: React.PropTypes.object.isRequired,
       onClose: React.PropTypes.func,
       // This data is supplied by the activeRoomStore.
       roomData: React.PropTypes.object.isRequired,
       savingContext: React.PropTypes.bool.isRequired,
       show: React.PropTypes.bool.isRequired
@@ -349,17 +347,17 @@ loop.roomViews = (function(mozL10n) {
         this.setState(newState);
       }
     },
 
     getInitialState: function() {
       var url = this._getURL();
       return {
         // `availableContext` prop only used in tests.
-        availableContext: this.props.availableContext,
+        availableContext: null,
         show: this.props.show,
         newRoomName: this.props.roomData.roomName || "",
         newRoomURL: url && url.location || "",
         newRoomDescription: url && url.description || "",
         newRoomThumbnail: url && url.thumbnail || ""
       };
     },
 
@@ -665,19 +663,19 @@ loop.roomViews = (function(mozL10n) {
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * stream is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
     _shouldRenderRemoteLoading: function() {
-      return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
-             !this.state.remoteSrcVideoObject &&
-             !this.state.mediaConnected;
+      return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+                !this.state.remoteSrcVideoObject &&
+                !this.state.mediaConnected);
     },
 
     handleAddContextClick: function() {
       this.setState({ showEditContext: true });
     },
 
     handleEditContextClick: function() {
       this.setState({ showEditContext: !this.state.showEditContext });
@@ -695,17 +693,17 @@ loop.roomViews = (function(mozL10n) {
       var localStreamClasses = React.addons.classSet({
         local: true,
         "local-stream": true,
         "local-stream-audio": this.state.videoMuted,
         "room-preview": this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS
       });
 
       var screenShareData = {
-        state: this.state.screenSharingState,
+        state: this.state.screenSharingState || SCREEN_SHARE_STATES.INACTIVE,
         visible: true
       };
 
       var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
       var shouldRenderEditContextView = this.state.contextEnabled && this.state.showEditContext;
       var roomData = this.props.roomStore.getStoreState("activeRoom");
 
       switch(this.state.roomState) {
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -280,18 +280,16 @@ loop.roomViews = (function(mozL10n) {
       );
     }
   });
 
   var DesktopRoomEditContextView = React.createClass({
     mixins: [React.addons.LinkedStateMixin],
 
     propTypes: {
-      // Only used for tests.
-      availableContext: React.PropTypes.object,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       error: React.PropTypes.object,
       mozLoop: React.PropTypes.object.isRequired,
       onClose: React.PropTypes.func,
       // This data is supplied by the activeRoomStore.
       roomData: React.PropTypes.object.isRequired,
       savingContext: React.PropTypes.bool.isRequired,
       show: React.PropTypes.bool.isRequired
@@ -349,17 +347,17 @@ loop.roomViews = (function(mozL10n) {
         this.setState(newState);
       }
     },
 
     getInitialState: function() {
       var url = this._getURL();
       return {
         // `availableContext` prop only used in tests.
-        availableContext: this.props.availableContext,
+        availableContext: null,
         show: this.props.show,
         newRoomName: this.props.roomData.roomName || "",
         newRoomURL: url && url.location || "",
         newRoomDescription: url && url.description || "",
         newRoomThumbnail: url && url.thumbnail || ""
       };
     },
 
@@ -665,19 +663,19 @@ loop.roomViews = (function(mozL10n) {
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * stream is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
     _shouldRenderRemoteLoading: function() {
-      return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
-             !this.state.remoteSrcVideoObject &&
-             !this.state.mediaConnected;
+      return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+                !this.state.remoteSrcVideoObject &&
+                !this.state.mediaConnected);
     },
 
     handleAddContextClick: function() {
       this.setState({ showEditContext: true });
     },
 
     handleEditContextClick: function() {
       this.setState({ showEditContext: !this.state.showEditContext });
@@ -695,17 +693,17 @@ loop.roomViews = (function(mozL10n) {
       var localStreamClasses = React.addons.classSet({
         local: true,
         "local-stream": true,
         "local-stream-audio": this.state.videoMuted,
         "room-preview": this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS
       });
 
       var screenShareData = {
-        state: this.state.screenSharingState,
+        state: this.state.screenSharingState || SCREEN_SHARE_STATES.INACTIVE,
         visible: true
       };
 
       var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
       var shouldRenderEditContextView = this.state.contextEnabled && this.state.showEditContext;
       var roomData = this.props.roomStore.getStoreState("activeRoom");
 
       switch(this.state.roomState) {
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -318,17 +318,17 @@ loop.shared.views = (function(_, l10n) {
           // If there's no getSources function, the sdk defines its own and caches
           // the result. So here we define the "normal" one which doesn't get cached, so
           // we can change it later.
           window.MediaStreamTrack.getSources = function(callback) {
             callback([{kind: "audio"}, {kind: "video"}]);
           };
         }
 
-        this.listenTo(this.props.sdk, "exception", this._handleSdkException.bind(this));
+        this.listenTo(this.props.sdk, "exception", this._handleSdkException);
 
         this.listenTo(this.props.model, "session:connected",
                                         this._onSessionConnected);
         this.listenTo(this.props.model, "session:stream-created",
                                         this._streamCreated);
         this.listenTo(this.props.model, ["session:peer-hungup",
                                          "session:network-disconnected",
                                          "session:ended"].join(" "),
@@ -420,24 +420,24 @@ loop.shared.views = (function(_, l10n) {
                       ev.preventDefault();
                     });
 
       this.listenTo(this.publisher, "streamCreated", function(ev) {
         this.setState({
           audio: {enabled: ev.stream.hasAudio},
           video: {enabled: ev.stream.hasVideo}
         });
-      }.bind(this));
+      });
 
       this.listenTo(this.publisher, "streamDestroyed", function() {
         this.setState({
           audio: {enabled: false},
           video: {enabled: false}
         });
-      }.bind(this));
+      });
 
       this.props.model.publish(this.publisher);
     },
 
     /**
      * Toggles streaming status for a given stream type.
      *
      * @param  {String}  type     Stream type ("audio" or "video").
@@ -537,17 +537,17 @@ loop.shared.views = (function(_, l10n) {
 
     getDefaultProps: function() {
       return {clearOnDocumentHidden: false};
     },
 
     componentDidMount: function() {
       this.listenTo(this.props.notifications, "reset add remove", function() {
         this.forceUpdate();
-      }.bind(this));
+      });
     },
 
     componentWillUnmount: function() {
       this.stopListening(this.props.notifications);
     },
 
     /**
      * Provided by DocumentVisibilityMixin. Clears notifications stack when the
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -318,17 +318,17 @@ loop.shared.views = (function(_, l10n) {
           // If there's no getSources function, the sdk defines its own and caches
           // the result. So here we define the "normal" one which doesn't get cached, so
           // we can change it later.
           window.MediaStreamTrack.getSources = function(callback) {
             callback([{kind: "audio"}, {kind: "video"}]);
           };
         }
 
-        this.listenTo(this.props.sdk, "exception", this._handleSdkException.bind(this));
+        this.listenTo(this.props.sdk, "exception", this._handleSdkException);
 
         this.listenTo(this.props.model, "session:connected",
                                         this._onSessionConnected);
         this.listenTo(this.props.model, "session:stream-created",
                                         this._streamCreated);
         this.listenTo(this.props.model, ["session:peer-hungup",
                                          "session:network-disconnected",
                                          "session:ended"].join(" "),
@@ -420,24 +420,24 @@ loop.shared.views = (function(_, l10n) {
                       ev.preventDefault();
                     });
 
       this.listenTo(this.publisher, "streamCreated", function(ev) {
         this.setState({
           audio: {enabled: ev.stream.hasAudio},
           video: {enabled: ev.stream.hasVideo}
         });
-      }.bind(this));
+      });
 
       this.listenTo(this.publisher, "streamDestroyed", function() {
         this.setState({
           audio: {enabled: false},
           video: {enabled: false}
         });
-      }.bind(this));
+      });
 
       this.props.model.publish(this.publisher);
     },
 
     /**
      * Toggles streaming status for a given stream type.
      *
      * @param  {String}  type     Stream type ("audio" or "video").
@@ -537,17 +537,17 @@ loop.shared.views = (function(_, l10n) {
 
     getDefaultProps: function() {
       return {clearOnDocumentHidden: false};
     },
 
     componentDidMount: function() {
       this.listenTo(this.props.notifications, "reset add remove", function() {
         this.forceUpdate();
-      }.bind(this));
+      });
     },
 
     componentWillUnmount: function() {
       this.stopListening(this.props.notifications);
     },
 
     /**
      * Provided by DocumentVisibilityMixin. Clears notifications stack when the
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -389,19 +389,19 @@ loop.standaloneRoomViews = (function(moz
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * stream is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
     _shouldRenderRemoteLoading: function() {
-      return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
-             !this.state.remoteSrcVideoObject &&
-             !this.state.mediaConnected;
+      return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+                !this.state.remoteSrcVideoObject &&
+                !this.state.mediaConnected);
     },
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * screen-share is on its way from the other user?
      *
      * @returns {boolean}
      * @private
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -389,19 +389,19 @@ loop.standaloneRoomViews = (function(moz
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * stream is on its way from the other user?
      *
      * @returns {boolean}
      * @private
      */
     _shouldRenderRemoteLoading: function() {
-      return this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
-             !this.state.remoteSrcVideoObject &&
-             !this.state.mediaConnected;
+      return !!(this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+                !this.state.remoteSrcVideoObject &&
+                !this.state.mediaConnected);
     },
 
     /**
      * Should we render a visual cue to the user (e.g. a spinner) that a remote
      * screen-share is on its way from the other user?
      *
      * @returns {boolean}
      * @private
--- a/browser/components/loop/test/desktop-local/contacts_test.js
+++ b/browser/components/loop/test/desktop-local/contacts_test.js
@@ -141,17 +141,18 @@ describe("loop.contacts", function() {
       // Sanity check the reverse:
       gravatars = node.querySelectorAll(".contact img[src=gravatarsDisabled]");
       expect(gravatars.length).to.equal(enabled ? 0 : fakeContacts.length);
     }
 
     it("should show the gravatars promo box", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          notifications: notifications
+          notifications: notifications,
+          startForm: function() {}
         }));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.not.equal(null);
 
       checkGravatarContacts(false);
     });
 
@@ -161,71 +162,76 @@ describe("loop.contacts", function() {
           return false;
         } else if (pref == "contacts.gravatars.show") {
           return true;
         }
         return "";
       };
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          notifications: notifications
+          notifications: notifications,
+          startForm: function() {}
         }));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
 
       checkGravatarContacts(true);
     });
 
     it("should hide the gravatars promo box when the 'use' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          notifications: notifications
+          notifications: notifications,
+          startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-accept"));
 
       sinon.assert.calledTwice(navigator.mozLoop.setLoopPref);
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
     });
 
     it("should should set the prefs correctly when the 'use' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          notifications: notifications
+          notifications: notifications,
+          startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-accept"));
 
       sinon.assert.calledTwice(navigator.mozLoop.setLoopPref);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref, "contacts.gravatars.promo", false);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref, "contacts.gravatars.show", true);
     });
 
     it("should hide the gravatars promo box when the 'close' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          notifications: notifications
+          notifications: notifications,
+          startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-close"));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
     });
 
     it("should set prefs correctly when the 'close' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          notifications: notifications
+          notifications: notifications,
+          startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-close"));
 
       sinon.assert.calledOnce(navigator.mozLoop.setLoopPref);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref,
         "contacts.gravatars.promo", false);
@@ -237,17 +243,18 @@ describe("loop.contacts", function() {
       navigator.mozLoop.calls = {
         startDirectCall: sandbox.stub(),
         clearCallInProgress: sandbox.stub()
       };
       navigator.mozLoop.contacts = {getAll: sandbox.stub()};
 
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          notifications: notifications
+          notifications: notifications,
+          startForm: function() {}
         }));
     });
 
     describe("#handleContactAction", function() {
       it("should call window.close when called with 'video-call' action",
         function() {
           listView.handleContactAction({}, "video-call");
 
@@ -294,18 +301,20 @@ describe("loop.contacts", function() {
 
   describe("ContactDetailsForm", function() {
     describe("#render", function() {
       describe("add mode", function() {
         var view;
 
         beforeEach(function() {
           view = TestUtils.renderIntoDocument(
-            React.createElement(
-              loop.contacts.ContactDetailsForm, {mode: "add"}));
+            React.createElement(loop.contacts.ContactDetailsForm, {
+              mode: "add",
+              selectTab: function() {}
+            }));
         });
 
         it("should render 'add' header", function() {
 
           var header = view.getDOMNode().querySelector("header");
           expect(header).to.not.equal(null);
           expect(header.textContent).to.eql(fakeAddContactButtonText);
         });
@@ -404,18 +413,20 @@ describe("loop.contacts", function() {
         });
       });
 
       describe("edit mode", function() {
         var view;
 
         beforeEach(function() {
           view = TestUtils.renderIntoDocument(
-            React.createElement(
-              loop.contacts.ContactDetailsForm, {mode: "edit"}));
+            React.createElement(loop.contacts.ContactDetailsForm, {
+              mode: "edit",
+              selectTab: function() {}
+            }));
         });
 
         it("should render 'edit' header", function() {
           var header = view.getDOMNode().querySelector("header");
           expect(header).to.not.equal(null);
           expect(header.textContent).to.eql(fakeEditContactButtonText);
         });
 
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -41,17 +41,20 @@ describe("loop.conversation", function()
         return {
           version: "42",
           channel: "test",
           platform: "test"
         };
       },
       getAudioBlob: sinon.spy(function(name, callback) {
         callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"}));
-      })
+      }),
+      getSelectedTabMetadata: function(callback) {
+        callback({});
+      }
     };
 
     fakeWindow = {
       navigator: { mozLoop: navigator.mozLoop },
       close: sinon.stub(),
       document: {},
       addEventListener: function() {},
       removeEventListener: function() {}
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -92,17 +92,17 @@
     describe("Uncaught Error Check", function() {
       it("should load the tests without errors", function() {
         chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
       });
     });
 
     describe("Unexpected Warnings Check", function() {
       it("should long only the warnings we expect", function() {
-        chai.expect(caughtWarnings.length).to.eql(128);
+        chai.expect(caughtWarnings.length).to.eql(30);
       });
     });
 
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
   </script>
 </body>
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -773,17 +773,17 @@ describe("loop.panel", function() {
           userDisplayName: fakeEmail
         }));
     }
 
     it("should dispatch a CreateRoom action when clicking on the Start a " +
        "conversation button",
       function() {
         navigator.mozLoop.userProfile = {email: fakeEmail};
-        var view = createTestComponent();
+        var view = createTestComponent(false);
 
         TestUtils.Simulate.click(view.getDOMNode().querySelector(".new-room-button"));
 
         sinon.assert.calledWith(dispatch, new sharedActions.CreateRoom({
           nameTemplate: "fakeText",
           roomOwner: fakeEmail
         }));
       });
@@ -796,17 +796,17 @@ describe("loop.panel", function() {
         callback({
           url: "http://invalid.com",
           description: "fakeSite",
           favicon: favicon,
           previews: ["fakeimage.png"]
         });
       };
 
-      var view = createTestComponent();
+      var view = createTestComponent(false);
 
       // Simulate being visible
       view.onDocumentVisible();
 
       var node = view.getDOMNode();
 
       // Select the checkbox
       TestUtils.Simulate.click(node.querySelector(".checkbox-wrapper"));
@@ -836,17 +836,17 @@ describe("loop.panel", function() {
       fakeMozLoop.getSelectedTabMetadata = function (callback) {
         callback({
           url: "https://www.example.com",
           description: "fake description",
           previews: [""]
         });
       };
 
-      var view = createTestComponent();
+      var view = createTestComponent(false);
 
       // Simulate being visible
       view.onDocumentVisible();
 
       var contextContent = view.getDOMNode().querySelector(".context-content");
       expect(contextContent).to.not.equal(null);
     });
 
@@ -854,17 +854,17 @@ describe("loop.panel", function() {
       fakeMozLoop.getSelectedTabMetadata = function (callback) {
         callback({
           url: "https://www.example.com",
           description: "fake description",
           previews: [""]
         });
       };
 
-      var view = createTestComponent();
+      var view = createTestComponent(false);
 
       view.setState({ checked: true });
 
       // Simulate being visible
       view.onDocumentVisible();
 
       expect(view.state.checked).eql(false);
     });
@@ -873,17 +873,17 @@ describe("loop.panel", function() {
       fakeMozLoop.getSelectedTabMetadata = function (callback) {
         callback({
           url: "https://www.example.com",
           description: "fake description",
           previews: [""]
         });
       };
 
-      var view = createTestComponent();
+      var view = createTestComponent(false);
 
       // Simulate being visible
       view.onDocumentVisible();
 
       var previewImage = view.getDOMNode().querySelector(".context-preview");
       expect(previewImage.src).to.match(/loop\/shared\/img\/icons-16x16.svg#globe$/);
     });
 
@@ -891,34 +891,34 @@ describe("loop.panel", function() {
       fakeMozLoop.getSelectedTabMetadata = function (callback) {
         callback({
           url: "",
           description: "fake description",
           previews: [""]
         });
       };
 
-      var view = createTestComponent();
+      var view = createTestComponent(false);
 
       view.onDocumentVisible();
 
       var contextInfo = view.getDOMNode().querySelector(".context");
       expect(contextInfo.classList.contains("hide")).to.equal(true);
     });
 
     it("should show only the hostname of the url", function() {
       fakeMozLoop.getSelectedTabMetadata = function (callback) {
         callback({
           url: "https://www.example.com:1234",
           description: "fake description",
           previews: [""]
         });
       };
 
-      var view = createTestComponent();
+      var view = createTestComponent(false);
 
       // Simulate being visible
       view.onDocumentVisible();
 
       var contextHostname = view.getDOMNode().querySelector(".context-url");
       expect(contextHostname.textContent).eql("www.example.com");
     });
 
@@ -928,17 +928,17 @@ describe("loop.panel", function() {
         callback({
           url: "https://www.example.com:1234",
           description: "fake description",
           favicon: favicon,
           previews: ["foo.gif"]
         });
       };
 
-      var view = createTestComponent();
+      var view = createTestComponent(false);
 
       // Simulate being visible.
       view.onDocumentVisible();
 
       var contextPreview = view.getDOMNode().querySelector(".context-preview");
       expect(contextPreview.src).eql(favicon);
     });
   });
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -136,18 +136,19 @@ describe("loop.roomViews", function () {
       view = null;
     });
 
     function mountTestComponent(props) {
       props = _.extend({
         dispatcher: dispatcher,
         mozLoop: fakeMozLoop,
         roomData: {},
+        savingContext: false,
         show: true,
-        showContext: false
+        showEditContext: false
       }, props);
       return TestUtils.renderIntoDocument(
         React.createElement(loop.roomViews.DesktopRoomInvitationView, props));
     }
 
     it("should dispatch an EmailRoomUrl action when the email button is pressed",
       function() {
         view = mountTestComponent({
@@ -242,100 +243,34 @@ describe("loop.roomViews", function () {
         var shareBtn = view.getDOMNode().querySelector(".btn-share");
 
         React.addons.TestUtils.Simulate.click(shareBtn);
 
         expect(view.state.showMenu).to.eql(true);
         expect(view.refs.menu.props.show).to.eql(true);
       });
     });
-
-    describe("Context", function() {
-      it("should not render the context data when told not to", function() {
-        view = mountTestComponent();
-
-        expect(view.getDOMNode().querySelector(".room-context")).to.eql(null);
-      });
-
-      it("should render context when data is available", function() {
-        view = mountTestComponent({
-          showContext: true,
-          roomData: {
-            roomContextUrls: [fakeContextURL]
-          }
-        });
-
-        expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
-      });
-
-      it("should render the context in editMode when the pencil is clicked", function() {
-        view = mountTestComponent({
-          showContext: true,
-          roomData: {
-            roomContextUrls: [fakeContextURL]
-          }
-        });
-
-        var pencil = view.getDOMNode().querySelector(".room-context-btn-edit");
-        expect(pencil).to.not.eql(null);
-
-        React.addons.TestUtils.Simulate.click(pencil);
-
-        expect(view.state.editMode).to.eql(true);
-        var node = view.getDOMNode();
-        expect(node.querySelector("form")).to.not.eql(null);
-        // No text paragraphs should be visible in editMode.
-        var visiblePs = Array.slice(node.querySelector("p")).filter(function(p) {
-          return p.classList.contains("hide") || p.classList.contains("error");
-        });
-        expect(visiblePs.length).to.eql(0);
-      });
-
-      it("should format the context url for display", function() {
-        sandbox.stub(sharedUtils, "formatURL").returns({
-          location: "location",
-          hostname: "hostname"
-        });
-
-        view = mountTestComponent({
-          showContext: true,
-          roomData: {
-            roomContextUrls: [fakeContextURL]
-          }
-        });
-
-        expect(view.getDOMNode().querySelector(".room-context-url").textContent)
-          .eql("hostname");
-      });
-
-      it("should show a default favicon when none is available", function() {
-        fakeContextURL.thumbnail = null;
-        view = mountTestComponent({
-          showContext: true,
-          roomData: {
-            roomContextUrls: [fakeContextURL]
-          }
-        });
-
-        expect(view.getDOMNode().querySelector(".room-context-thumbnail").src)
-          .to.match(/loop\/shared\/img\/icons-16x16.svg#globe$/);
-      });
-    });
   });
 
   describe("DesktopRoomConversationView", function() {
     var view;
 
     beforeEach(function() {
       loop.store.StoreMixin.register({
         feedbackStore: new loop.store.FeedbackStore(dispatcher, {
           feedbackClient: {}
         })
       });
       sandbox.stub(dispatcher, "dispatch");
+      fakeMozLoop.getLoopPref = function(prefName) {
+        if (prefName == "contextInConversations.enabled") {
+          return true;
+        }
+        return "test";
+      };
     });
 
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         React.createElement(loop.roomViews.DesktopRoomConversationView, {
           dispatcher: dispatcher,
           roomStore: roomStore,
           mozLoop: fakeMozLoop
@@ -736,17 +671,17 @@ describe("loop.roomViews", function () {
             provider: fakeProvider,
             roomUrl: "http://example.com",
             previews: []
           }));
       });
     });
   });
 
-  describe("DesktopRoomContextView", function() {
+  describe("DesktopRoomEditContextView", function() {
     var view;
 
     afterEach(function() {
       view = null;
     });
 
     function mountTestComponent(props) {
       props = _.extend({
@@ -754,45 +689,20 @@ describe("loop.roomViews", function () {
         mozLoop: fakeMozLoop,
         savingContext: false,
         show: true,
         roomData: {
           roomToken: "fakeToken"
         }
       }, props);
       return TestUtils.renderIntoDocument(
-        React.createElement(loop.roomViews.DesktopRoomContextView, props));
+        React.createElement(loop.roomViews.DesktopRoomEditContextView, props));
     }
 
     describe("#render", function() {
-      it("should show the context information properly when available", function() {
-        view = mountTestComponent({
-          roomData: {
-            roomDescription: "Hello, is it me you're looking for?",
-            roomContextUrls: [fakeContextURL]
-          }
-        });
-
-        var node = view.getDOMNode();
-        expect(node).to.not.eql(null);
-        expect(node.querySelector(".room-context-thumbnail").src).to.
-          eql(fakeContextURL.thumbnail);
-        expect(node.querySelector(".room-context-description").firstChild.textContent).
-          to.eql(fakeContextURL.description);
-      });
-
-      it("should not render optional data", function() {
-        view = mountTestComponent({
-          roomData: { roomContextUrls: [fakeContextURL] }
-        });
-
-        expect(view.getDOMNode().querySelector(".room-context-comment")).to.
-          eql(null);
-      });
-
       it("should not render the component when 'show' is false", function() {
         view = mountTestComponent({
           show: false
         });
 
         expect(view.getDOMNode()).to.eql(null);
       });
 
@@ -801,95 +711,60 @@ describe("loop.roomViews", function () {
           roomData: { roomContextUrls: [fakeContextURL] }
         });
 
         var closeBtn = view.getDOMNode().querySelector(".room-context-btn-close");
         React.addons.TestUtils.Simulate.click(closeBtn);
         expect(view.getDOMNode()).to.eql(null);
       });
 
-      it("should render the view in editMode when appropriate", function() {
+      it("should render the view correctly", function() {
         var roomName = "Hello, is it me you're looking for?";
         view = mountTestComponent({
-          editMode: true,
           roomData: {
             roomName: roomName,
             roomContextUrls: [fakeContextURL]
           }
         });
 
         var node = view.getDOMNode();
         expect(node.querySelector("form")).to.not.eql(null);
         // Check the contents of the form fields.
         expect(node.querySelector(".room-context-name").value).to.eql(roomName);
         expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
         expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
       });
 
       it("should show the checkbox as disabled when context is already set", function() {
         view = mountTestComponent({
-          editMode: true,
           roomData: {
             roomToken: "fakeToken",
             roomName: "fakeName",
             roomContextUrls: [fakeContextURL]
           }
         });
 
         var checkbox = view.getDOMNode().querySelector(".checkbox");
         expect(checkbox.classList.contains("disabled")).to.eql(true);
       });
 
-      it("should render the editMode view when the edit button is clicked", function(done) {
-        var roomName = "Hello, is it me you're looking for?";
-        view = mountTestComponent({
-          roomData: {
-            roomToken: "fakeToken",
-            roomName: roomName,
-            roomContextUrls: [fakeContextURL]
-          }
-        });
-
-        // Switch to editMode via setting the prop, since we can control that
-        // better.
-        view.setProps({ editMode: true }, function() {
-          // First check if availableContext is set correctly.
-          expect(view.state.availableContext).to.not.eql(null);
-          expect(view.state.availableContext.previewImage).to.eql(favicon);
-
-          var node = view.getDOMNode();
-          expect(node.querySelector(".checkbox-wrapper").classList.contains("disabled")).to.eql(true);
-          expect(node.querySelector(".room-context-name").value).to.eql(roomName);
-          expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
-          expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
-
-          done();
-        });
-      });
-
-      it("should hide the checkbox when no context data is stored or available", function(done) {
+      it("should hide the checkbox when no context data is stored or available", function() {
         view = mountTestComponent({
           roomData: {
             roomToken: "fakeToken",
             roomName: "Hello, is it me you're looking for?"
           }
         });
 
-        // Switch to editMode via setting the prop, since we can control that
-        // better.
-        view.setProps({ editMode: true }, function() {
-          // First check if availableContext is set correctly.
-          expect(view.state.availableContext).to.not.eql(null);
-          expect(view.state.availableContext.previewImage).to.eql(favicon);
+        // First check if availableContext is set correctly.
+        expect(view.state.availableContext).to.not.eql(null);
+        expect(view.state.availableContext.previewImage).to.eql(favicon);
 
-          var node = view.getDOMNode();
-          expect(node.querySelector(".checkbox-wrapper").classList.contains("hide")).to.eql(true);
-
-          done();
-        });
+        var node = view.getDOMNode();
+        expect(node.querySelector(".checkbox-wrapper").classList.contains("hide")).to.eql(true);
       });
     });
 
     describe("Update Room", function() {
       var roomNameBox;
 
       beforeEach(function() {
         sandbox.stub(dispatcher, "dispatch");
@@ -947,35 +822,34 @@ describe("loop.roomViews", function () {
       it("should close the edit form when context was saved successfully", function(done) {
         view.setProps({ savingContext: true }, function() {
           var node = view.getDOMNode();
           // The button should show up as disabled.
           expect(node.querySelector(".btn-info").hasAttribute("disabled")).to.eql(true);
 
           // Now simulate a successful save.
           view.setProps({ savingContext: false }, function() {
-            // The editMode flag should be updated.
-            expect(view.state.editMode).to.eql(false);
+            // The 'show flag should be updated.
+            expect(view.state.show).to.eql(false);
             done();
           });
         });
       });
     });
 
     describe("#handleCheckboxChange", function() {
       var node, checkbox;
 
       beforeEach(function() {
+        fakeMozLoop.getSelectedTabMetadata = sinon.stub().callsArgWith(0, {
+          favicon: fakeContextURL.thumbnail,
+          title: fakeContextURL.description,
+          url: fakeContextURL.location
+        });
         view = mountTestComponent({
-          availableContext: {
-            description: fakeContextURL.description,
-            previewImage: fakeContextURL.thumbnail,
-            url: fakeContextURL.location
-          },
-          editMode: true,
           roomData: {
             roomToken: "fakeToken",
             roomName: "fakeName"
           }
         });
 
         node = view.getDOMNode();
         checkbox = node.querySelector(".checkbox");
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -91,17 +91,17 @@
     describe("Uncaught Error Check", function() {
       it("should load the tests without errors", function() {
         chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
       });
     });
 
     describe("Unexpected Warnings Check", function() {
       it("should long only the warnings we expect", function() {
-        chai.expect(caughtWarnings.length).to.eql(180);
+        chai.expect(caughtWarnings.length).to.eql(73);
       });
     });
 
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
   </script>
 </body>
--- a/browser/components/loop/test/shared/textChatView_test.js
+++ b/browser/components/loop/test/shared/textChatView_test.js
@@ -38,34 +38,37 @@ describe("loop.shared.views.TextChatView
   });
 
   describe("TextChatEntriesView", function() {
     var view;
 
     function mountTestComponent(extraProps) {
       var basicProps = {
         dispatcher: dispatcher,
-        messageList: []
+        messageList: [],
+        useDesktopPaths: false
       };
 
       return TestUtils.renderIntoDocument(
         React.createElement(loop.shared.views.chat.TextChatEntriesView,
           _.extend(basicProps, extraProps)));
     }
 
     it("should render message entries when message were sent/ received", function() {
       view = mountTestComponent({
         messageList: [{
           type: CHAT_MESSAGE_TYPES.RECEIVED,
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Hello!"
+          message: "Hello!",
+          receivedTimestamp: "2015-06-25T17:53:55.357Z"
         }, {
           type: CHAT_MESSAGE_TYPES.SENT,
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Is it me you're looking for?"
+          message: "Is it me you're looking for?",
+          sentTimestamp: "2015-06-25T17:53:55.357Z"
         }]
       });
 
       var node = view.getDOMNode();
       expect(node).to.not.eql(null);
 
       var entries = node.querySelectorAll(".text-chat-entry");
       expect(entries.length).to.eql(2);
@@ -76,112 +79,121 @@ describe("loop.shared.views.TextChatView
     it("should play a sound when a message is received", function() {
       view = mountTestComponent();
       sandbox.stub(view, "play");
 
       view.setProps({
         messageList: [{
           type: CHAT_MESSAGE_TYPES.RECEIVED,
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Hello!"
+          message: "Hello!",
+          receivedTimestamp: "2015-06-25T17:53:55.357Z"
         }]
       });
 
       sinon.assert.calledOnce(view.play);
       sinon.assert.calledWithExactly(view.play, "message");
     });
 
     it("should not play a sound when a special message is displayed", function() {
       view = mountTestComponent();
       sandbox.stub(view, "play");
 
       view.setProps({
         messageList: [{
           type: CHAT_MESSAGE_TYPES.SPECIAL,
           contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
-          message: "Hello!"
+          message: "Hello!",
+          receivedTimestamp: "2015-06-25T17:53:55.357Z"
         }]
       });
 
       sinon.assert.notCalled(view.play);
     });
 
     it("should not play a sound when a message is sent", function() {
       view = mountTestComponent();
       sandbox.stub(view, "play");
 
       view.setProps({
         messageList: [{
           type: CHAT_MESSAGE_TYPES.SENT,
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Hello!"
+          message: "Hello!",
+          sentTimestamp: "2015-06-25T17:53:55.357Z"
         }]
       });
 
       sinon.assert.notCalled(view.play);
     });
   });
 
   describe("TextChatEntry", function() {
     var view;
 
     function mountTestComponent(extraProps) {
       var props = _.extend({
-        dispatcher: dispatcher
+        contentType: CHAT_CONTENT_TYPES.TEXT,
+        dispatcher: dispatcher,
+        message: "test",
+        type: CHAT_MESSAGE_TYPES.RECEIVED,
+        timestamp: "2015-06-23T22:48:39.738Z"
       }, extraProps);
       return TestUtils.renderIntoDocument(
         React.createElement(loop.shared.views.chat.TextChatEntry, props));
     }
 
     it("should not render a timestamp", function() {
       view = mountTestComponent({
-        showTimestamp: false,
-        timestamp: "2015-06-23T22:48:39.738Z"
+        showTimestamp: false
       });
       var node = view.getDOMNode();
 
       expect(node.querySelector(".text-chat-entry-timestamp")).to.eql(null);
     });
 
     it("should render a timestamp", function() {
       view = mountTestComponent({
-        showTimestamp: true,
-        timestamp: "2015-06-23T22:48:39.738Z"
+        showTimestamp: true
       });
       var node = view.getDOMNode();
 
       expect(node.querySelector(".text-chat-entry-timestamp")).to.not.eql(null);
     });
   });
 
   describe("TextChatEntriesView", function() {
     var view, node;
 
     function mountTestComponent(extraProps) {
       var props = _.extend({
-        dispatcher: dispatcher
+        dispatcher: dispatcher,
+        messageList: [],
+        useDesktopPaths: false
       }, extraProps);
       return TestUtils.renderIntoDocument(
         React.createElement(loop.shared.views.chat.TextChatEntriesView, props));
     }
 
     beforeEach(function() {
       store.setStoreState({ textChatEnabled: true });
     });
 
     it("should show timestamps if there are different senders", function() {
       view = mountTestComponent({
         messageList: [{
           type: CHAT_MESSAGE_TYPES.RECEIVED,
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Hello!"
+          message: "Hello!",
+          receivedTimestamp: "2015-06-25T17:53:55.357Z"
         }, {
           type: CHAT_MESSAGE_TYPES.SENT,
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Is it me you're looking for?"
+          message: "Is it me you're looking for?",
+          sentTimestamp: "2015-06-25T17:53:55.357Z"
         }]
       });
       node = view.getDOMNode();
 
       expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
           .to.eql(2);
     });
 
@@ -225,36 +237,39 @@ describe("loop.shared.views.TextChatView
           .to.eql(2);
     });
 
     it("should not show timestamps from msgs sent in the same minute", function() {
       view = mountTestComponent({
         messageList: [{
           type: CHAT_MESSAGE_TYPES.RECEIVED,
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Hello!"
+          message: "Hello!",
+          receivedTimestamp: "2015-06-25T17:53:55.357Z"
         }, {
           type: CHAT_MESSAGE_TYPES.RECEIVED,
           contentType: CHAT_CONTENT_TYPES.TEXT,
-          message: "Is it me you're looking for?"
+          message: "Is it me you're looking for?",
+          sentTimestamp: "2015-06-25T17:53:55.357Z"
         }]
       });
       node = view.getDOMNode();
 
       expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
           .to.eql(1);
     });
   });
 
   describe("TextChatView", function() {
     var view;
 
     function mountTestComponent(extraProps) {
       var props = _.extend({
         dispatcher: dispatcher,
+        showRoomName: false,
         useDesktopPaths: false
       }, extraProps);
       return TestUtils.renderIntoDocument(
         React.createElement(loop.shared.views.chat.TextChatView, props));
     }
 
     beforeEach(function() {
       store.setStoreState({ textChatEnabled: true });
@@ -284,25 +299,25 @@ describe("loop.shared.views.TextChatView
       });
 
       var node = view.getDOMNode();
 
       expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
           .to.eql(2);
     });
 
-    it("should display the view if no messages and text chat not enabled", function() {
+    it("should not display the view if no messages and text chat not enabled", function() {
       store.setStoreState({ textChatEnabled: false });
 
       view = mountTestComponent();
 
-      expect(view.getDOMNode()).not.eql(null);
+      expect(view.getDOMNode()).eql(null);
     });
 
-    it("should display the view if text chat is enabled", function() {
+    it("should display the view if no messages and text chat is enabled", function() {
       view = mountTestComponent();
 
       expect(view.getDOMNode()).not.eql(null);
     });
 
     it("should display only the text chat box if entry is enabled but there are no messages", function() {
       view = mountTestComponent();
 
@@ -312,21 +327,25 @@ describe("loop.shared.views.TextChatView
       expect(node.querySelector(".text-chat-entries")).eql(null);
     });
 
     it("should render message entries when message were sent/ received", function() {
       view = mountTestComponent();
 
       store.receivedTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
-        message: "Hello!"
+        message: "Hello!",
+        sentTimestamp: "1970-01-01T00:03:00.000Z",
+        receivedTimestamp: "1970-01-01T00:03:00.000Z"
       });
       store.sendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
-        message: "Is it me you're looking for?"
+        message: "Is it me you're looking for?",
+        sentTimestamp: "1970-01-01T00:03:00.000Z",
+        receivedTimestamp: "1970-01-01T00:03:00.000Z"
       });
 
       var node = view.getDOMNode();
       expect(node.querySelector(".text-chat-entries")).to.not.eql(null);
 
       var entries = node.querySelectorAll(".text-chat-entry");
       expect(entries.length).to.eql(2);
       expect(entries[0].classList.contains("received")).to.eql(true);
@@ -341,27 +360,28 @@ describe("loop.shared.views.TextChatView
         message: "Foo",
         timestamp: 0
       });
 
       expect(node.querySelector(".sent")).to.not.eql(null);
     });
 
     it("should add `received` CSS class selector to msg of type RECEIVED",
-       function() {
-         var node = mountTestComponent().getDOMNode();
+      function() {
+        var node = mountTestComponent().getDOMNode();
 
-         store.receivedTextChatMessage({
-           contentType: CHAT_CONTENT_TYPES.TEXT,
-           message: "Foo",
-           timestamp: 0
-         });
+        store.receivedTextChatMessage({
+          contentType: CHAT_CONTENT_TYPES.TEXT,
+          message: "Foo",
+          sentTimestamp: "1970-01-01T00:03:00.000Z",
+          receivedTimestamp: "1970-01-01T00:03:00.000Z"
+        });
 
-         expect(node.querySelector(".received")).to.not.eql(null);
-     });
+        expect(node.querySelector(".received")).to.not.eql(null);
+      });
 
     it("should render a room name special entry", function() {
       view = mountTestComponent({
         showRoomName: true
       });
 
       store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
         roomName: "A wonderful surprise!",
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -251,16 +251,19 @@ describe("loop.shared.views", function()
           new sharedActions.EndScreenShare({}));
       });
   });
 
   describe("ConversationToolbar", function() {
     var hangup, publishStream;
 
     function mountTestComponent(props) {
+      props = _.extend({
+        dispatcher: dispatcher
+      }, props || {});
       return TestUtils.renderIntoDocument(
         React.createElement(sharedViews.ConversationToolbar, props));
     }
 
     beforeEach(function() {
       hangup = sandbox.stub();
       publishStream = sandbox.stub();
     });
@@ -357,16 +360,19 @@ describe("loop.shared.views", function()
       sinon.assert.calledWithExactly(publishStream, "video", true);
     });
   });
 
   describe("ConversationView", function() {
     var fakeSDK, fakeSessionData, fakeSession, fakePublisher, model, fakeAudio;
 
     function mountTestComponent(props) {
+      props = _.extend({
+        dispatcher: dispatcher
+      }, props || {});
       return TestUtils.renderIntoDocument(
         React.createElement(sharedViews.ConversationView, props));
     }
 
     beforeEach(function() {
       fakeAudio = {
         play: sinon.spy(),
         pause: sinon.spy(),
@@ -671,16 +677,19 @@ describe("loop.shared.views", function()
       });
     });
   });
 
   describe("NotificationListView", function() {
     var coll, view, testNotif;
 
     function mountTestComponent(props) {
+      props = _.extend({
+        key: 0
+      }, props || {});
       return TestUtils.renderIntoDocument(
         React.createElement(sharedViews.NotificationListView, props));
     }
 
     beforeEach(function() {
       coll = new sharedModels.NotificationCollection();
       view = mountTestComponent({notifications: coll});
       testNotif = {level: "warning", message: "foo"};
@@ -826,17 +835,21 @@ describe("loop.shared.views", function()
     });
   });
 
   describe("ContextUrlView", function() {
     var view;
 
     function mountTestComponent(extraProps) {
       var props = _.extend({
-        dispatcher: dispatcher
+        allowClick: false,
+        description: "test",
+        dispatcher: dispatcher,
+        showContextTitle: false,
+        useDesktopPaths: false
       }, extraProps);
       return TestUtils.renderIntoDocument(
         React.createElement(sharedViews.ContextUrlView, props));
     }
 
     it("should display nothing if the url is invalid", function() {
       view = mountTestComponent({
         url: "fjrTykyw"
@@ -908,16 +921,19 @@ describe("loop.shared.views", function()
         }));
     });
   });
 
   describe("MediaView", function() {
     var view;
 
     function mountTestComponent(props) {
+      props = _.extend({
+        isLoading: false
+      }, props || {});
       return TestUtils.renderIntoDocument(
         React.createElement(sharedViews.MediaView, props));
     }
 
     it("should display an avatar view", function() {
       view = mountTestComponent({
         displayAvatar: true,
         mediaType: "local"
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -83,17 +83,17 @@
     describe("Uncaught Error Check", function() {
       it("should load the tests without errors", function() {
         chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
       });
     });
 
     describe("Unexpected Warnings Check", function() {
       it("should long only the warnings we expect", function() {
-        chai.expect(caughtWarnings.length).to.eql(37);
+        chai.expect(caughtWarnings.length).to.eql(36);
       });
     });
 
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
 </script>
 </body>
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -1308,17 +1308,17 @@
         setTimeout(waitForQueuedFrames, 500);
         return;
       }
       // Put the title back, in case views changed it.
       document.title = "Loop UI Components Showcase";
 
       // This simulates the mocha layout for errors which means we can run
       // this alongside our other unit tests but use the same harness.
-      var expectedWarningsCount = 53;
+      var expectedWarningsCount = 29;
       var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
       if (uncaughtError || warningsMismatch) {
         $("#results").append("<div class='failures'><em>" +
           (!!(uncaughtError && warningsMismatch) ? 2 : 1) + "</em></div>");
         if (warningsMismatch) {
           $("#results").append("<li class='test fail'>" +
             "<h2>Unexpected number of warnings detected in UI-Showcase</h2>" +
             "<pre class='error'>Got: " + caughtWarnings.length + "\n" +
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -1308,17 +1308,17 @@
         setTimeout(waitForQueuedFrames, 500);
         return;
       }
       // Put the title back, in case views changed it.
       document.title = "Loop UI Components Showcase";
 
       // This simulates the mocha layout for errors which means we can run
       // this alongside our other unit tests but use the same harness.
-      var expectedWarningsCount = 53;
+      var expectedWarningsCount = 29;
       var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
       if (uncaughtError || warningsMismatch) {
         $("#results").append("<div class='failures'><em>" +
           (!!(uncaughtError && warningsMismatch) ? 2 : 1) + "</em></div>");
         if (warningsMismatch) {
           $("#results").append("<li class='test fail'>" +
             "<h2>Unexpected number of warnings detected in UI-Showcase</h2>" +
             "<pre class='error'>Got: " + caughtWarnings.length + "\n" +