Bug 1078718 - Force standalone app reload on hashchange event. r=Standard8 a=loop-only
authorAndrei Oprea <andrei.br92@gmail.com>
Tue, 28 Oct 2014 21:52:17 +0000
changeset 233779 88c62ec930b790553b611e33805d0d02f5583101
parent 233778 6d62a7439d3f2782411422d2da4fc40b1d9fd66c
child 233780 72bcb9582e285928aaaf33369b96aa56339a77fc
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, loop-only
bugs1078718
milestone35.0a2
Bug 1078718 - Force standalone app reload on hashchange event. r=Standard8 a=loop-only
browser/components/loop/content/shared/js/mixins.js
browser/components/loop/standalone/content/js/webapp.js
browser/components/loop/standalone/content/js/webapp.jsx
browser/components/loop/test/shared/mixins_test.js
browser/components/loop/test/standalone/webapp_test.js
--- a/browser/components/loop/content/shared/js/mixins.js
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -22,16 +22,47 @@ loop.shared.mixins = (function() {
    * @param {Object}
    */
   function setRootObject(obj) {
     console.info("loop.shared.mixins: rootObject set to " + obj);
     rootObject = obj;
   }
 
   /**
+   * window.location mixin. Handles changes in the call url.
+   * Forces a reload of the page to ensure proper state of the webapp
+   *
+   * @type {Object}
+   */
+  var UrlHashChangeMixin = {
+    propTypes: {
+      onUrlHashChange: React.PropTypes.func.isRequired
+    },
+
+    componentDidMount: function() {
+      rootObject.addEventListener("hashchange", this.onUrlHashChange, false);
+    },
+
+    componentWillUnmount: function() {
+      rootObject.removeEventListener("hashchange", this.onUrlHashChange, false);
+    }
+  };
+
+  /**
+   * Document location mixin.
+   *
+   * @type {Object}
+   */
+  var DocumentLocationMixin = {
+    locationReload: function() {
+      rootObject.location.reload();
+    }
+  };
+
+  /**
    * Dropdown menu mixin.
    * @type {Object}
    */
   var DropdownMenuMixin = {
     get documentBody() {
       return rootObject.document.body;
     },
 
@@ -146,11 +177,13 @@ loop.shared.mixins = (function() {
       this._ensureAudioStopped();
     }
   };
 
   return {
     AudioMixin: AudioMixin,
     setRootObject: setRootObject,
     DropdownMenuMixin: DropdownMenuMixin,
-    DocumentVisibilityMixin: DocumentVisibilityMixin
+    DocumentVisibilityMixin: DocumentVisibilityMixin,
+    DocumentLocationMixin: DocumentLocationMixin,
+    UrlHashChangeMixin: UrlHashChangeMixin
   };
 })();
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -840,16 +840,20 @@ loop.webapp = (function($, _, OT, mozL10
     },
   });
 
   /**
    * Webapp Root View. This is the main, single, view that controls the display
    * of the webapp page.
    */
   var WebappRootView = React.createClass({displayName: 'WebappRootView',
+
+    mixins: [sharedMixins.UrlHashChangeMixin,
+             sharedMixins.DocumentLocationMixin],
+
     propTypes: {
       client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
       conversation: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(sharedModels.ConversationModel),
         React.PropTypes.instanceOf(FxOSConversationModel)
       ]).isRequired,
       helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
       notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
@@ -860,16 +864,20 @@ loop.webapp = (function($, _, OT, mozL10
 
     getInitialState: function() {
       return {
         unsupportedDevice: this.props.helper.isIOS(navigator.platform),
         unsupportedBrowser: !this.props.sdk.checkSystemRequirements(),
       };
     },
 
+    onUrlHashChange: function() {
+      this.locationReload();
+    },
+
     render: function() {
       if (this.state.unsupportedDevice) {
         return UnsupportedDeviceView(null);
       } else if (this.state.unsupportedBrowser) {
         return UnsupportedBrowserView(null);
       } else if (this.props.conversation.get("loopToken")) {
         return (
           OutgoingConversationView({
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -840,16 +840,20 @@ loop.webapp = (function($, _, OT, mozL10
     },
   });
 
   /**
    * Webapp Root View. This is the main, single, view that controls the display
    * of the webapp page.
    */
   var WebappRootView = React.createClass({
+
+    mixins: [sharedMixins.UrlHashChangeMixin,
+             sharedMixins.DocumentLocationMixin],
+
     propTypes: {
       client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
       conversation: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(sharedModels.ConversationModel),
         React.PropTypes.instanceOf(FxOSConversationModel)
       ]).isRequired,
       helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
       notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
@@ -860,16 +864,20 @@ loop.webapp = (function($, _, OT, mozL10
 
     getInitialState: function() {
       return {
         unsupportedDevice: this.props.helper.isIOS(navigator.platform),
         unsupportedBrowser: !this.props.sdk.checkSystemRequirements(),
       };
     },
 
+    onUrlHashChange: function() {
+      this.locationReload();
+    },
+
     render: function() {
       if (this.state.unsupportedDevice) {
         return <UnsupportedDeviceView />;
       } else if (this.state.unsupportedBrowser) {
         return <UnsupportedBrowserView />;
       } else if (this.props.conversation.get("loopToken")) {
         return (
           <OutgoingConversationView
--- a/browser/components/loop/test/shared/mixins_test.js
+++ b/browser/components/loop/test/shared/mixins_test.js
@@ -16,16 +16,85 @@ describe("loop.shared.mixins", function(
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
+  describe("loop.webapp.UrlHashChangeMixin", function() {
+    function createTestComponent(onUrlHashChange) {
+      var TestComp = React.createClass({
+        mixins: [loop.shared.mixins.UrlHashChangeMixin],
+        onUrlHashChange: onUrlHashChange || function(){},
+        render: function() {
+          return React.DOM.div();
+        }
+      });
+      return new TestComp();
+    }
+
+    it("should watch for hashchange event", function() {
+      var addEventListener = sandbox.spy();
+      sharedMixins.setRootObject({
+        addEventListener: addEventListener
+      });
+
+      TestUtils.renderIntoDocument(createTestComponent());
+
+      sinon.assert.calledOnce(addEventListener);
+      sinon.assert.calledWith(addEventListener, "hashchange");
+    });
+
+    it("should call onUrlHashChange when the url is updated", function() {
+      sharedMixins.setRootObject({
+        addEventListener: function(name, cb) {
+          if (name === "hashchange") {
+            cb();
+          }
+        }
+      });
+      var onUrlHashChange = sandbox.stub();
+
+      TestUtils.renderIntoDocument(createTestComponent(onUrlHashChange));
+
+      sinon.assert.calledOnce(onUrlHashChange);
+    });
+  });
+
+  describe("loop.webapp.DocumentLocationMixin", function() {
+    var reloadStub, TestComp;
+
+    beforeEach(function() {
+      reloadStub = sandbox.stub();
+
+      sharedMixins.setRootObject({
+        location: {
+          reload: reloadStub
+        }
+      });
+
+      TestComp = React.createClass({
+        mixins: [loop.shared.mixins.DocumentLocationMixin],
+        render: function() {
+          return React.DOM.div();
+        }
+      });
+    });
+
+    it("should call window.location.reload", function() {
+      var comp = TestUtils.renderIntoDocument(TestComp());
+
+      comp.locationReload();
+
+      sinon.assert.calledOnce(reloadStub);
+    });
+  });
+
   describe("loop.panel.DocumentVisibilityMixin", function() {
     var comp, TestComp, onDocumentVisibleStub, onDocumentHiddenStub;
 
     beforeEach(function() {
       onDocumentVisibleStub = sandbox.stub();
       onDocumentHiddenStub = sandbox.stub();
 
       TestComp = React.createClass({
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -509,17 +509,18 @@ describe("loop.webapp", function() {
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         loop.webapp.WebappRootView({
         client: client,
         helper: helper,
         notifications: notifications,
         sdk: sdk,
         conversation: conversationModel,
-        feedbackApiClient: feedbackApiClient
+        feedbackApiClient: feedbackApiClient,
+        onUrlHashChange: sandbox.stub()
       }));
     }
 
     beforeEach(function() {
       helper = new sharedUtils.Helper();
       sdk = {
         checkSystemRequirements: function() { return true; }
       };