Bug 1107255-Fix tested Loop callers of window.close to use WindowCloseMixin, r=NiKo`
authorDan Mosedale <dmose@meer.net>
Tue, 09 Dec 2014 12:04:25 -0800
changeset 218959 bcc39a19cd51062912bb621a77652c32e600d1bc
parent 218958 13c5e6f5657d4b24717cc71af6b55247130f3133
child 218960 81daac845113f5d10f0310d63b0d36db1d906595
push id27950
push usercbook@mozilla.com
push dateWed, 10 Dec 2014 10:58:50 +0000
treeherdermozilla-central@5b01216f97f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersNiKo
bugs1107255
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1107255-Fix tested Loop callers of window.close to use WindowCloseMixin, r=NiKo`
browser/components/loop/content/js/conversation.js
browser/components/loop/content/js/conversation.jsx
browser/components/loop/content/js/conversationViews.js
browser/components/loop/content/js/conversationViews.jsx
browser/components/loop/content/shared/js/mixins.js
browser/components/loop/test/desktop-local/contacts_test.js
browser/components/loop/test/desktop-local/conversationViews_test.js
browser/components/loop/test/desktop-local/conversation_test.js
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -218,17 +218,17 @@ loop.conversation = (function(mozL10n) {
 
   /**
    * This view manages the incoming conversation views - from
    * call initiation through to the actual conversation and call end.
    *
    * At the moment, it does more than that, these parts need refactoring out.
    */
   var IncomingConversationView = React.createClass({displayName: 'IncomingConversationView',
-    mixins: [sharedMixins.AudioMixin],
+    mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin],
 
     propTypes: {
       client: React.PropTypes.instanceOf(loop.Client).isRequired,
       conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
                          .isRequired,
       sdk: React.PropTypes.object.isRequired,
       conversationAppStore: React.PropTypes.instanceOf(
         loop.store.ConversationAppStore).isRequired,
@@ -310,17 +310,17 @@ loop.conversation = (function(mozL10n) {
           return (
             sharedViews.FeedbackView({
               feedbackStore: this.props.feedbackStore, 
               onAfterFeedbackReceived: this.closeWindow.bind(this)}
             )
           );
         }
         case "close": {
-          window.close();
+          this.closeWindow();
           return (React.DOM.div(null));
         }
       }
     },
 
     /**
      * Notify the user that the connection was not possible
      * @param {{code: number, message: string}} error
@@ -454,20 +454,16 @@ loop.conversation = (function(mozL10n) {
      */
     _abortIncomingCall: function() {
       this._websocket.close();
       // Having a timeout here lets the logging for the websocket complete and be
       // displayed on the console if both are on.
       setTimeout(this.closeWindow, 0);
     },
 
-    closeWindow: function() {
-      window.close();
-    },
-
     /**
      * Accepts an incoming call.
      */
     accept: function() {
       navigator.mozLoop.stopAlerting();
       this._websocket.accept();
       this.props.conversation.accepted();
     },
@@ -536,17 +532,17 @@ loop.conversation = (function(mozL10n) {
     },
   });
 
   /**
    * Master controller view for handling if incoming or outgoing calls are
    * in progress, and hence, which view to display.
    */
   var AppControllerView = React.createClass({displayName: 'AppControllerView',
-    mixins: [Backbone.Events],
+    mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
 
     propTypes: {
       // XXX Old types required for incoming call view.
       client: React.PropTypes.instanceOf(loop.Client).isRequired,
       conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
                          .isRequired,
       sdk: React.PropTypes.object.isRequired,
 
@@ -570,20 +566,16 @@ loop.conversation = (function(mozL10n) {
         this.setState(this.props.conversationAppStore.getStoreState());
       }, this);
     },
 
     componentWillUnmount: function() {
       this.stopListening(this.props.conversationAppStore);
     },
 
-    closeWindow: function() {
-      window.close();
-    },
-
     render: function() {
       switch(this.state.windowType) {
         case "incoming": {
           return (IncomingConversationView({
             client: this.props.client, 
             conversation: this.props.conversation, 
             sdk: this.props.sdk, 
             conversationAppStore: this.props.conversationAppStore, 
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -218,17 +218,17 @@ loop.conversation = (function(mozL10n) {
 
   /**
    * This view manages the incoming conversation views - from
    * call initiation through to the actual conversation and call end.
    *
    * At the moment, it does more than that, these parts need refactoring out.
    */
   var IncomingConversationView = React.createClass({
-    mixins: [sharedMixins.AudioMixin],
+    mixins: [sharedMixins.AudioMixin, sharedMixins.WindowCloseMixin],
 
     propTypes: {
       client: React.PropTypes.instanceOf(loop.Client).isRequired,
       conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
                          .isRequired,
       sdk: React.PropTypes.object.isRequired,
       conversationAppStore: React.PropTypes.instanceOf(
         loop.store.ConversationAppStore).isRequired,
@@ -310,17 +310,17 @@ loop.conversation = (function(mozL10n) {
           return (
             <sharedViews.FeedbackView
               feedbackStore={this.props.feedbackStore}
               onAfterFeedbackReceived={this.closeWindow.bind(this)}
             />
           );
         }
         case "close": {
-          window.close();
+          this.closeWindow();
           return (<div/>);
         }
       }
     },
 
     /**
      * Notify the user that the connection was not possible
      * @param {{code: number, message: string}} error
@@ -454,20 +454,16 @@ loop.conversation = (function(mozL10n) {
      */
     _abortIncomingCall: function() {
       this._websocket.close();
       // Having a timeout here lets the logging for the websocket complete and be
       // displayed on the console if both are on.
       setTimeout(this.closeWindow, 0);
     },
 
-    closeWindow: function() {
-      window.close();
-    },
-
     /**
      * Accepts an incoming call.
      */
     accept: function() {
       navigator.mozLoop.stopAlerting();
       this._websocket.accept();
       this.props.conversation.accepted();
     },
@@ -536,17 +532,17 @@ loop.conversation = (function(mozL10n) {
     },
   });
 
   /**
    * Master controller view for handling if incoming or outgoing calls are
    * in progress, and hence, which view to display.
    */
   var AppControllerView = React.createClass({
-    mixins: [Backbone.Events],
+    mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
 
     propTypes: {
       // XXX Old types required for incoming call view.
       client: React.PropTypes.instanceOf(loop.Client).isRequired,
       conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
                          .isRequired,
       sdk: React.PropTypes.object.isRequired,
 
@@ -570,20 +566,16 @@ loop.conversation = (function(mozL10n) {
         this.setState(this.props.conversationAppStore.getStoreState());
       }, this);
     },
 
     componentWillUnmount: function() {
       this.stopListening(this.props.conversationAppStore);
     },
 
-    closeWindow: function() {
-      window.close();
-    },
-
     render: function() {
       switch(this.state.windowType) {
         case "incoming": {
           return (<IncomingConversationView
             client={this.props.client}
             conversation={this.props.conversation}
             sdk={this.props.sdk}
             conversationAppStore={this.props.conversationAppStore}
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -188,17 +188,21 @@ loop.conversationViews = (function(mozL1
       );
     }
   });
 
   /**
    * Call failed view. Displayed when a call fails.
    */
   var CallFailedView = React.createClass({displayName: 'CallFailedView',
-    mixins: [Backbone.Events, sharedMixins.AudioMixin],
+    mixins: [
+      Backbone.Events,
+      sharedMixins.AudioMixin,
+      sharedMixins.WindowCloseMixin
+    ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       store: React.PropTypes.instanceOf(
         loop.store.ConversationStore).isRequired,
       contact: React.PropTypes.object.isRequired,
       // This is used by the UI showcase.
       emailLinkError: React.PropTypes.bool,
@@ -222,17 +226,17 @@ loop.conversationViews = (function(mozL1
     componentWillUnmount: function() {
       this.stopListening(this.props.store);
     },
 
     _onEmailLinkReceived: function() {
       var emailLink = this.props.store.getStoreState("emailLink");
       var contactEmail = _getPreferredEmail(this.props.contact).value;
       sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
-      window.close();
+      this.closeWindow();
     },
 
     _onEmailLinkError: function() {
       this.setState({
         emailLinkError: true,
         emailLinkButtonDisabled: false
       });
     },
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -188,17 +188,21 @@ loop.conversationViews = (function(mozL1
       );
     }
   });
 
   /**
    * Call failed view. Displayed when a call fails.
    */
   var CallFailedView = React.createClass({
-    mixins: [Backbone.Events, sharedMixins.AudioMixin],
+    mixins: [
+      Backbone.Events,
+      sharedMixins.AudioMixin,
+      sharedMixins.WindowCloseMixin
+    ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       store: React.PropTypes.instanceOf(
         loop.store.ConversationStore).isRequired,
       contact: React.PropTypes.object.isRequired,
       // This is used by the UI showcase.
       emailLinkError: React.PropTypes.bool,
@@ -222,17 +226,17 @@ loop.conversationViews = (function(mozL1
     componentWillUnmount: function() {
       this.stopListening(this.props.store);
     },
 
     _onEmailLinkReceived: function() {
       var emailLink = this.props.store.getStoreState("emailLink");
       var contactEmail = _getPreferredEmail(this.props.contact).value;
       sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
-      window.close();
+      this.closeWindow();
     },
 
     _onEmailLinkError: function() {
       this.setState({
         emailLinkError: true,
         emailLinkButtonDisabled: false
       });
     },
--- a/browser/components/loop/content/shared/js/mixins.js
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -19,17 +19,17 @@ loop.shared.mixins = (function() {
    * Sets a new root object.  This is useful for testing native DOM events so we
    * can fake them. In beforeEach(), loop.shared.mixins.setRootObject is used to
    * substitute a fake window, and in afterEach(), the real window object is
    * replaced.
    *
    * @param {Object}
    */
   function setRootObject(obj) {
-    console.info("loop.shared.mixins: rootObject set to " + obj);
+    console.log("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}
--- a/browser/components/loop/test/desktop-local/contacts_test.js
+++ b/browser/components/loop/test/desktop-local/contacts_test.js
@@ -30,17 +30,16 @@ describe("loop.contacts", function() {
           textContentValue = fakeDoneButtonText;
         }
         return JSON.stringify({textContent: textContentValue});
       },
     };
 
     fakeWindow = {
       close: sandbox.stub(),
-      //document: { addEventListener: function(){} }
     };
     loop.shared.mixins.setRootObject(fakeWindow);
 
     document.mozL10n.initialize(navigator.mozLoop);
     // XXX prevent a race whenever mozL10n hasn't been initialized yet
     setTimeout(done, 0);
   });
 
--- a/browser/components/loop/test/desktop-local/conversationViews_test.js
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -3,17 +3,17 @@
 
 var expect = chai.expect;
 
 describe("loop.conversationViews", function () {
   "use strict";
 
   var sharedUtils = loop.shared.utils;
   var sandbox, oldTitle, view, dispatcher, contact, fakeAudioXHR;
-  var fakeMozLoop;
+  var fakeMozLoop, fakeWindow;
 
   var CALL_STATES = loop.store.CALL_STATES;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
 
     oldTitle = document.title;
     sandbox.stub(document.mozL10n, "get", function(x) {
@@ -53,19 +53,27 @@ describe("loop.conversationViews", funct
           channel: "test",
           platform: "test"
         };
       },
       getAudioBlob: sinon.spy(function(name, callback) {
         callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"}));
       })
     };
+
+    fakeWindow = {
+      navigator: { mozLoop: fakeMozLoop },
+      close: sandbox.stub(),
+    };
+    loop.shared.mixins.setRootObject(fakeWindow);
+
   });
 
   afterEach(function() {
+    loop.shared.mixins.setRootObject(window);
     document.title = oldTitle;
     view = undefined;
     delete navigator.mozLoop;
     sandbox.restore();
   });
 
   describe("CallIdentifierView", function() {
     function mountTestComponent(props) {
@@ -311,22 +319,21 @@ describe("loop.conversationViews", funct
 
       sinon.assert.calledOnce(composeCallUrlEmail);
       sinon.assert.calledWithExactly(composeCallUrlEmail,
         "http://fake.invalid/", "test@test.tld");
     });
 
     it("should close the conversation window once the email link is received",
       function() {
-        sandbox.stub(window, "close");
         view = mountTestComponent();
 
         store.setStoreState({emailLink: "http://fake.invalid/"});
 
-        sinon.assert.calledOnce(window.close);
+        sinon.assert.calledOnce(fakeWindow.close);
       });
 
     it("should display an error message in case email link retrieval failed",
       function() {
         view = mountTestComponent();
 
         store.trigger("error:emailLink");
 
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -6,16 +6,17 @@
 
 var expect = chai.expect;
 
 describe("loop.conversation", function() {
   "use strict";
 
   var sharedModels = loop.shared.models,
       sharedView = loop.shared.views,
+      fakeWindow,
       sandbox;
 
   // XXX refactor to Just Work with "sandbox.stubComponent" or else
   // just pass in the sandbox and put somewhere generally usable
 
   function stubComponent(obj, component, mockTagName){
     var reactClass = React.createClass({
       render: function() {
@@ -63,25 +64,32 @@ describe("loop.conversation", function()
           platform: "test"
         };
       },
       getAudioBlob: sinon.spy(function(name, callback) {
         callback(null, new Blob([new ArrayBuffer(10)], {type: 'audio/ogg'}));
       })
     };
 
+    fakeWindow = {
+      navigator: { mozLoop: navigator.mozLoop },
+      close: sandbox.stub(),
+    };
+    loop.shared.mixins.setRootObject(fakeWindow);
+
     // XXX These stubs should be hoisted in a common file
     // Bug 1040968
     sandbox.stub(document.mozL10n, "get", function(x) {
       return x;
     });
     document.mozL10n.initialize(navigator.mozLoop);
   });
 
   afterEach(function() {
+    loop.shared.mixins.setRootObject(window);
     delete navigator.mozLoop;
     sandbox.restore();
   });
 
   describe("#init", function() {
     beforeEach(function() {
       sandbox.stub(React, "renderComponent");
       sandbox.stub(document.mozL10n, "initialize");
@@ -403,17 +411,16 @@ describe("loop.conversation", function()
             // setup functions
             icView = mountTestComponent();
             promise = new Promise(function(resolve, reject) {
               resolve();
             });
 
             sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect").returns(promise);
             sandbox.stub(loop.CallConnectionWebSocket.prototype, "close");
-            sandbox.stub(window, "close");
           });
 
           describe("progress - terminated (previousState = alerting)", function() {
             it("should stop alerting", function(done) {
               promise.then(function() {
                 icView._websocket.trigger("progress", {
                   state: "terminated",
                   reason: "timeout"
@@ -440,22 +447,23 @@ describe("loop.conversation", function()
               promise.then(function() {
                 icView._websocket.trigger("progress", {
                   state: "terminated",
                   reason: "answered-elsewhere"
                 }, "alerting");
 
                 sandbox.clock.tick(1);
 
-                sinon.assert.calledOnce(window.close);
+                sinon.assert.calledOnce(fakeWindow.close);
                 done();
               });
             });
           });
 
+
           describe("progress - terminated (previousState not init" +
                    " nor alerting)",
             function() {
               it("should set the state to end", function(done) {
                 promise.then(function() {
                   icView._websocket.trigger("progress", {
                     state: "terminated",
                     reason: "media-fail"
@@ -516,17 +524,16 @@ describe("loop.conversation", function()
           sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
         });
       });
 
       describe("#decline", function() {
         beforeEach(function() {
           icView = mountTestComponent();
 
-          sandbox.stub(window, "close");
           icView._websocket = {
             decline: sinon.stub(),
             close: sinon.stub()
           };
           conversation.set({
             windowId: "8699"
           });
           conversation.setIncomingSessionData({
@@ -534,17 +541,17 @@ describe("loop.conversation", function()
           });
         });
 
         it("should close the window", function() {
           icView.decline();
 
           sandbox.clock.tick(1);
 
-          sinon.assert.calledOnce(window.close);
+          sinon.assert.calledOnce(fakeWindow.close);
         });
 
         it("should stop alerting", function() {
           icView.decline();
 
           sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
         });
 
@@ -562,17 +569,16 @@ describe("loop.conversation", function()
 
         beforeEach(function() {
           icView = mountTestComponent();
 
           icView._websocket = {
             decline: sinon.spy(),
             close: sinon.stub()
           };
-          sandbox.stub(window, "close");
 
           mozLoop = {
             LOOP_SESSION_TYPE: {
               GUEST: 1,
               FXA: 2
             }
           };
 
@@ -621,17 +627,17 @@ describe("loop.conversation", function()
           sinon.assert.calledWithExactly(log, fakeError);
         });
 
         it("should close the window", function() {
           icView.declineAndBlock();
 
           sandbox.clock.tick(1);
 
-          sinon.assert.calledOnce(window.close);
+          sinon.assert.calledOnce(fakeWindow.close);
         });
       });
     });
 
     describe("Events", function() {
       var fakeSessionData;
 
       beforeEach(function() {