Bug 1132301: Part 5 - add unit tests for the Social Sharing feature of Loop. r=Standard8, a=lizzard
authorMike de Boer <mdeboer@mozilla.com>
Thu, 16 Apr 2015 14:00:56 +0200
changeset 265626 60e700d0c8084e535ba461c5a9efacfebe9828a1
parent 265625 739c89e40bcdac800843cccce3ba88a9aa288cdd
child 265627 de154381b9bf864fb52f248362145a04bbeebdf7
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, lizzard
bugs1132301
milestone39.0a2
Bug 1132301: Part 5 - add unit tests for the Social Sharing feature of Loop. r=Standard8, a=lizzard
browser/components/loop/test/desktop-local/panel_test.js
browser/components/loop/test/desktop-local/roomViews_test.js
browser/components/loop/test/shared/activeRoomStore_test.js
browser/components/loop/test/shared/roomStore_test.js
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -24,17 +24,18 @@ describe("loop.panel", function() {
     // https://github.com/cjohansen/Sinon.JS/issues/393
     fakeXHR.xhr.onCreate = function (xhr) {
       requests.push(xhr);
     };
 
     fakeWindow = {
       close: sandbox.stub(),
       addEventListener: function() {},
-      document: { addEventListener: function(){} }
+      document: { addEventListener: function(){} },
+      setTimeout: function(callback) { callback(); }
     };
     loop.shared.mixins.setRootObject(fakeWindow);
 
     notifications = new loop.shared.models.NotificationCollection();
 
     fakeMozLoop = navigator.mozLoop = {
       doNotDisturb: true,
       fxAEnabled: true,
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -13,27 +13,29 @@ describe("loop.roomViews", function () {
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
 
     dispatcher = new loop.Dispatcher();
 
     fakeMozLoop = {
       getAudioBlob: sinon.stub(),
-      getLoopPref: sinon.stub()
+      getLoopPref: sinon.stub(),
+      isSocialShareButtonAvailable: sinon.stub()
     };
 
     fakeWindow = {
       close: sinon.stub(),
       document: {},
       navigator: {
         mozLoop: fakeMozLoop
       },
       addEventListener: function() {},
-      removeEventListener: function() {}
+      removeEventListener: function() {},
+      setTimeout: function(callback) { callback(); }
     };
     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;
     });
@@ -193,16 +195,31 @@ describe("loop.roomViews", function () {
           var copyBtn = view.getDOMNode().querySelector('.btn-copy');
 
           React.addons.TestUtils.Simulate.click(copyBtn);
 
           // copied_url_button is the l10n string.
           expect(copyBtn.textContent).eql("copied_url_button");
       });
     });
+
+    describe("Share button", function() {
+      beforeEach(function() {
+        view = mountTestComponent();
+      });
+
+      it("should toggle the share dropdown when the share button is clicked", 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("DesktopRoomConversationView", function() {
     var view;
 
     beforeEach(function() {
       loop.store.StoreMixin.register({
         feedbackStore: new loop.store.FeedbackStore(dispatcher, {
@@ -428,9 +445,145 @@ describe("loop.roomViews", function () {
 
           view = mountTestComponent();
 
           expect(view.getDOMNode().querySelector(".local-stream-audio"))
             .not.eql(null);
         });
     });
   });
+
+  describe("SocialShareDropdown", function() {
+    var view, fakeProvider;
+
+    beforeEach(function() {
+      sandbox.stub(dispatcher, "dispatch");
+
+      fakeProvider = {
+        name: "foo",
+        origin: "https://foo",
+        iconURL: "http://example.com/foo.png"
+      };
+    });
+
+    afterEach(function() {
+      view = fakeProvider = null;
+    });
+
+    function mountTestComponent() {
+      return TestUtils.renderIntoDocument(
+        React.createElement(loop.roomViews.SocialShareDropdown, {
+          dispatcher: dispatcher,
+          roomStore: roomStore,
+          show: true
+        })
+      );
+    }
+
+    describe("#render", function() {
+      it("should show no contents when the Social Providers have not been fetched yet", function() {
+        view = mountTestComponent();
+
+        expect(view.getDOMNode()).to.eql(null);
+      });
+
+      it("should show different contents when the Share XUL button is not available", function() {
+        activeRoomStore.setStoreState({
+          socialShareProviders: []
+        });
+        view = mountTestComponent();
+
+        var node = view.getDOMNode();
+        expect(node.querySelector(".share-panel-header")).to.not.eql(null);
+      });
+
+      it("should show an empty list when no Social Providers are available", function() {
+        activeRoomStore.setStoreState({
+          socialShareButtonAvailable: true,
+          socialShareProviders: []
+        });
+
+        view = mountTestComponent();
+
+        var node = view.getDOMNode();
+        expect(node.querySelector(".icon-add-share-service")).to.not.eql(null);
+        expect(node.querySelectorAll(".dropdown-menu-item").length).to.eql(1);
+      });
+
+      it("should show a list of available Social Providers", function() {
+        activeRoomStore.setStoreState({
+          socialShareButtonAvailable: true,
+          socialShareProviders: [fakeProvider]
+        });
+
+        view = mountTestComponent();
+
+        var node = view.getDOMNode();
+        expect(node.querySelector(".icon-add-share-service")).to.not.eql(null);
+        expect(node.querySelector(".dropdown-menu-separator")).to.not.eql(null);
+
+        var dropdownNodes = node.querySelectorAll(".dropdown-menu-item");
+        expect(dropdownNodes.length).to.eql(2);
+        expect(dropdownNodes[1].querySelector("img").src).to.eql(fakeProvider.iconURL);
+        expect(dropdownNodes[1].querySelector("span").textContent)
+          .to.eql(fakeProvider.name);
+      });
+    });
+
+    describe("#handleToolbarAddButtonClick", function() {
+      it("should dispatch an action when the 'add to toolbar' button is clicked", function() {
+        activeRoomStore.setStoreState({
+          socialShareProviders: []
+        });
+
+        view = mountTestComponent();
+
+        var addButton = view.getDOMNode().querySelector(".btn-toolbar-add");
+        React.addons.TestUtils.Simulate.click(addButton);
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.AddSocialShareButton());
+      });
+    });
+
+    describe("#handleAddServiceClick", function() {
+      it("should dispatch an action when the 'add provider' item is clicked", function() {
+        activeRoomStore.setStoreState({
+          socialShareProviders: [],
+          socialShareButtonAvailable: true
+        });
+
+        view = mountTestComponent();
+
+        var addItem = view.getDOMNode().querySelector(".dropdown-menu-item:first-child");
+        React.addons.TestUtils.Simulate.click(addItem);
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.AddSocialShareProvider());
+      });
+    });
+
+    describe("#handleProviderClick", function() {
+      it("should dispatch an action when a provider item is clicked", function() {
+        activeRoomStore.setStoreState({
+          roomUrl: "http://example.com",
+          socialShareButtonAvailable: true,
+          socialShareProviders: [fakeProvider]
+        });
+
+        view = mountTestComponent();
+
+        var providerItem = view.getDOMNode().querySelector(".dropdown-menu-item:last-child");
+        React.addons.TestUtils.Simulate.click(providerItem);
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.ShareRoomUrl({
+            provider: fakeProvider,
+            roomUrl: activeRoomStore.getStoreState().roomUrl,
+            previews: []
+          }));
+      });
+    });
+  });
 });
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -29,17 +29,19 @@ describe("loop.store.ActiveRoomStore", f
         get: sinon.stub(),
         join: sinon.stub(),
         refreshMembership: sinon.stub(),
         leave: sinon.stub(),
         on: sinon.stub(),
         off: sinon.stub()
       },
       setScreenShareState: sinon.stub(),
-      getActiveTabWindowId: sandbox.stub().callsArgWith(0, null, 42)
+      getActiveTabWindowId: sandbox.stub().callsArgWith(0, null, 42),
+      isSocialShareButtonAvailable: sinon.stub().returns(false),
+      getSocialShareProviders: sinon.stub().returns([])
     };
 
     fakeSdkDriver = {
       connectSession: sinon.stub(),
       disconnectSession: sinon.stub(),
       forceDisconnectAll: sinon.stub().callsArg(0),
       retryPublishWithoutVideo: sinon.stub(),
       startScreenShare: sinon.stub(),
@@ -272,17 +274,19 @@ describe("loop.store.ActiveRoomStore", f
           windowId: "42",
           type: "room",
           roomToken: fakeToken
         }));
 
         sinon.assert.calledTwice(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.SetupRoomInfo(_.extend({
-            roomToken: fakeToken
+            roomToken: fakeToken,
+            socialShareButtonAvailable: false,
+            socialShareProviders: []
           }, fakeRoomData)));
       });
 
     it("should dispatch a JoinRoom action if the get is successful",
       function() {
         store.setupWindowData(new sharedActions.SetupWindowData({
           windowId: "42",
           type: "room",
@@ -408,17 +412,19 @@ describe("loop.store.ActiveRoomStore", f
   describe("#setupRoomInfo", function() {
     var fakeRoomInfo;
 
     beforeEach(function() {
       fakeRoomInfo = {
         roomName: "Its a room",
         roomOwner: "Me",
         roomToken: "fakeToken",
-        roomUrl: "http://invalid"
+        roomUrl: "http://invalid",
+        socialShareButtonAvailable: false,
+        socialShareProviders: []
       };
     });
 
     it("should set the state to READY", function() {
       store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomInfo));
 
       expect(store._storeState.roomState).eql(ROOM_STATES.READY);
     });
@@ -426,16 +432,18 @@ describe("loop.store.ActiveRoomStore", f
     it("should save the room information", function() {
       store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomInfo));
 
       var state = store.getStoreState();
       expect(state.roomName).eql(fakeRoomInfo.roomName);
       expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
       expect(state.roomToken).eql(fakeRoomInfo.roomToken);
       expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
+      expect(state.socialShareButtonAvailable).eql(false);
+      expect(state.socialShareProviders).eql([]);
     });
   });
 
   describe("#updateRoomInfo", function() {
     var fakeRoomInfo;
 
     beforeEach(function() {
       fakeRoomInfo = {
@@ -450,16 +458,41 @@ describe("loop.store.ActiveRoomStore", f
 
       var state = store.getStoreState();
       expect(state.roomName).eql(fakeRoomInfo.roomName);
       expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
       expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
     });
   });
 
+  describe("#updateSocialShareInfo", function() {
+    var fakeSocialShareInfo;
+
+    beforeEach(function() {
+      fakeSocialShareInfo = {
+        socialShareButtonAvailable: true,
+        socialShareProviders: [{
+          name: "foo",
+          origin: "https://example.com",
+          iconURL: "icon.png"
+        }]
+      };
+    });
+
+    it("should save the Social API information", function() {
+      store.updateSocialShareInfo(new sharedActions.UpdateSocialShareInfo(fakeSocialShareInfo));
+
+      var state = store.getStoreState();
+      expect(state.socialShareButtonAvailable)
+        .eql(fakeSocialShareInfo.socialShareButtonAvailable);
+      expect(state.socialShareProviders)
+        .eql(fakeSocialShareInfo.socialShareProviders);
+    });
+  });
+
   describe("#joinRoom", function() {
     it("should reset failureReason", function() {
       store.setStoreState({failureReason: "Test"});
 
       store.joinRoom();
 
       expect(store.getStoreState().failureReason).eql(undefined);
     });
@@ -1089,24 +1122,46 @@ describe("loop.store.ActiveRoomStore", f
 
     it("should set the state to ENDED", function() {
       store.leaveRoom();
 
       expect(store._storeState.roomState).eql(ROOM_STATES.ENDED);
     });
   });
 
+  describe("#_handleSocialShareUpdate", function() {
+    it("should dispatch an UpdateRoomInfo action", function() {
+      store._handleSocialShareUpdate();
+
+      sinon.assert.calledOnce(dispatcher.dispatch);
+      sinon.assert.calledWithExactly(dispatcher.dispatch,
+        new sharedActions.UpdateSocialShareInfo({
+          socialShareButtonAvailable: false,
+          socialShareProviders: []
+        }));
+    });
+
+    it("should call respective mozLoop methods", function() {
+      store._handleSocialShareUpdate();
+
+      sinon.assert.calledOnce(fakeMozLoop.isSocialShareButtonAvailable);
+      sinon.assert.calledOnce(fakeMozLoop.getSocialShareProviders);
+    });
+  });
+
   describe("Events", function() {
     describe("update:{roomToken}", function() {
       beforeEach(function() {
         store.setupRoomInfo(new sharedActions.SetupRoomInfo({
           roomName: "Its a room",
           roomOwner: "Me",
           roomToken: "fakeToken",
-          roomUrl: "http://invalid"
+          roomUrl: "http://invalid",
+          socialShareButtonAvailable: false,
+          socialShareProviders: []
         }));
       });
 
       it("should dispatch an UpdateRoomInfo action", function() {
         sinon.assert.calledTwice(fakeMozLoop.rooms.on);
 
         var fakeRoomData = {
           roomName: "fakeName",
@@ -1126,17 +1181,22 @@ describe("loop.store.ActiveRoomStore", f
       var fakeRoomData = {
         roomName: "Its a room",
         roomOwner: "Me",
         roomToken: "fakeToken",
         roomUrl: "http://invalid"
       };
 
       beforeEach(function() {
-        store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomData));
+        store.setupRoomInfo(new sharedActions.SetupRoomInfo(
+          _.extend(fakeRoomData, {
+            socialShareButtonAvailable: false,
+            socialShareProviders: []
+          })
+        ));
       });
 
       it("should disconnect all room connections", function() {
         fakeMozLoop.rooms.on.callArgWith(1, "delete:" + fakeRoomData.roomToken, fakeRoomData);
 
         sinon.assert.calledOnce(fakeSdkDriver.forceDisconnectAll);
       });
 
--- a/browser/components/loop/test/shared/roomStore_test.js
+++ b/browser/components/loop/test/shared/roomStore_test.js
@@ -72,16 +72,19 @@ describe("loop.store.RoomStore", functio
       pendingInitialRetrieval: false,
       rooms: [],
       activeRoom: {}
     };
 
     beforeEach(function() {
       fakeMozLoop = {
         copyString: function() {},
+        getLoopPref: function(pref) {
+          return pref;
+        },
         notifyUITour: function() {},
         rooms: {
           create: function() {},
           getAll: function() {},
           open: function() {},
           rename: function() {},
           on: sandbox.stub()
         }
@@ -363,16 +366,72 @@ describe("loop.store.RoomStore", functio
         }));
 
         sinon.assert.calledOnce(sharedUtils.composeCallUrlEmail);
         sinon.assert.calledWithExactly(sharedUtils.composeCallUrlEmail,
           "http://invalid");
       });
     });
 
+    describe("#shareRoomUrl", function() {
+      beforeEach(function() {
+        fakeMozLoop.socialShareRoom = sinon.stub();
+      });
+
+      it("should pass the correct data for GMail sharing", function() {
+        var roomUrl = "http://invalid";
+        var origin = "https://mail.google.com/v1";
+        store.shareRoomUrl(new sharedActions.ShareRoomUrl({
+          roomUrl: roomUrl,
+          provider: {
+            origin: origin
+          }
+        }));
+
+        sinon.assert.calledOnce(fakeMozLoop.socialShareRoom);
+        sinon.assert.calledWithExactly(fakeMozLoop.socialShareRoom, origin,
+          roomUrl, "", "");
+      });
+
+      it("should pass the correct data for all other Social Providers", function() {
+        var roomUrl = "http://invalid2";
+        var origin = "https://twitter.com/share";
+        store.shareRoomUrl(new sharedActions.ShareRoomUrl({
+          roomUrl: roomUrl,
+          provider: {
+            origin: origin
+          }
+        }));
+
+        sinon.assert.calledOnce(fakeMozLoop.socialShareRoom);
+        sinon.assert.calledWithExactly(fakeMozLoop.socialShareRoom, origin,
+          roomUrl, "", null);
+      });
+    });
+
+    describe("#addSocialShareButton", function() {
+      it("should invoke to the correct mozLoop function", function() {
+        fakeMozLoop.addSocialShareButton = sinon.stub();
+
+        store.addSocialShareButton(new sharedActions.AddSocialShareButton());
+
+        sinon.assert.calledOnce(fakeMozLoop.addSocialShareButton);
+      });
+    });
+
+    describe("#addSocialShareProvider", function() {
+      it("should invoke to the correct mozLoop function", function() {
+        fakeMozLoop.addSocialShareProvider = sinon.stub();
+
+        store.addSocialShareProvider(new sharedActions.AddSocialShareProvider());
+
+        sinon.assert.calledOnce(fakeMozLoop.addSocialShareProvider);
+      });
+    });
+
     describe("#setStoreState", function() {
       it("should update store state data", function() {
         store.setStoreState({pendingCreation: true});
 
         expect(store.getStoreState().pendingCreation).eql(true);
       });
 
       it("should trigger a `change` event", function(done) {