Bug 1198841 - Brief message to invite someone to join when joining a room with someone already there [r=Standard8]
authorEd Lee <edilee@mozilla.com>
Fri, 28 Aug 2015 01:39:59 -0700
changeset 261290 ba7eec9f555db8ed6387fe1606574dfdc0bf9c8f
parent 261289 fecf8cae8265a9283fa94bf2da62612dc0d1b0c5
child 261291 466847c2206cbfb6dc4c287d16e910cd93a99d87
push id64705
push usercbook@mozilla.com
push dateTue, 08 Sep 2015 14:02:43 +0000
treeherdermozilla-inbound@7fa38a962661 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1198841
milestone43.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 1198841 - Brief message to invite someone to join when joining a room with someone already there [r=Standard8]
browser/components/loop/content/js/roomViews.js
browser/components/loop/content/js/roomViews.jsx
browser/components/loop/content/shared/js/activeRoomStore.js
browser/components/loop/test/desktop-local/roomViews_test.js
browser/components/loop/test/shared/activeRoomStore_test.js
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -588,18 +588,30 @@ loop.roomViews = (function(mozL10n) {
     publishStream: function(type, enabled) {
       this.props.dispatcher.dispatch(
         new sharedActions.SetMute({
           type: type,
           enabled: enabled
         }));
     },
 
+    /**
+     * Determine if the invitation controls should be shown.
+     *
+     * @return {Boolean} True if there's no guests.
+     */
     _shouldRenderInvitationOverlay: function() {
-      return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
+      var hasGuests = typeof this.state.participants === "object" &&
+        this.state.participants.filter(function(participant) {
+          return !participant.owner;
+        }).length > 0;
+
+      // Don't show if the room has participants whether from the room state or
+      // there being non-owner guests in the participants array.
+      return this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS && !hasGuests;
     },
 
     /**
      * Works out if remote video should be rended or not, depending on the
      * room state and other flags.
      *
      * @return {Boolean} True if remote video should be rended.
      *
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -588,18 +588,30 @@ loop.roomViews = (function(mozL10n) {
     publishStream: function(type, enabled) {
       this.props.dispatcher.dispatch(
         new sharedActions.SetMute({
           type: type,
           enabled: enabled
         }));
     },
 
+    /**
+     * Determine if the invitation controls should be shown.
+     *
+     * @return {Boolean} True if there's no guests.
+     */
     _shouldRenderInvitationOverlay: function() {
-      return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
+      var hasGuests = typeof this.state.participants === "object" &&
+        this.state.participants.filter(function(participant) {
+          return !participant.owner;
+        }).length > 0;
+
+      // Don't show if the room has participants whether from the room state or
+      // there being non-owner guests in the participants array.
+      return this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS && !hasGuests;
     },
 
     /**
      * Works out if remote video should be rended or not, depending on the
      * room state and other flags.
      *
      * @return {Boolean} True if remote video should be rended.
      *
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -46,16 +46,17 @@ loop.store.ActiveRoomStore = (function()
 
   var ROOM_STATES = loop.store.ROOM_STATES;
 
   var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
 
   var OPTIONAL_ROOMINFO_FIELDS = {
     urls: "roomContextUrls",
     description: "roomDescription",
+    participants: "participants",
     roomInfoFailure: "roomInfoFailure",
     roomName: "roomName",
     roomState: "roomState"
   };
 
   /**
    * Active room store.
    *
@@ -291,16 +292,17 @@ loop.store.ActiveRoomStore = (function()
             this.dispatchAction(new sharedActions.RoomFailure({
               error: error,
               failedJoinRequest: false
             }));
             return;
           }
 
           this.dispatchAction(new sharedActions.SetupRoomInfo({
+            participants: roomData.participants,
             roomToken: actionData.roomToken,
             roomContextUrls: roomData.decryptedContext.urls,
             roomDescription: roomData.decryptedContext.description,
             roomName: roomData.decryptedContext.roomName,
             roomUrl: roomData.roomUrl,
             socialShareProviders: this._mozLoop.getSocialShareProviders()
           }));
 
@@ -413,16 +415,17 @@ loop.store.ActiveRoomStore = (function()
      */
     setupRoomInfo: function(actionData) {
       if (this._onUpdateListener) {
         console.error("Room info already set up!");
         return;
       }
 
       this.setStoreState({
+        participants: actionData.participants,
         roomContextUrls: actionData.roomContextUrls,
         roomDescription: actionData.roomDescription,
         roomName: actionData.roomName,
         roomState: ROOM_STATES.READY,
         roomToken: actionData.roomToken,
         roomUrl: actionData.roomUrl,
         socialShareProviders: actionData.socialShareProviders
       });
@@ -444,17 +447,17 @@ loop.store.ActiveRoomStore = (function()
      */
     updateRoomInfo: function(actionData) {
       var newState = {
         roomUrl: actionData.roomUrl
       };
       // Iterate over the optional fields that _may_ be present on the actionData
       // object.
       Object.keys(OPTIONAL_ROOMINFO_FIELDS).forEach(function(field) {
-        if (actionData[field]) {
+        if (actionData[field] !== undefined) {
           newState[OPTIONAL_ROOMINFO_FIELDS[field]] = actionData[field];
         }
       });
       this.setStoreState(newState);
     },
 
     /**
      * Handles the updateSocialShareInfo action. Updates the room data with new
@@ -473,16 +476,17 @@ loop.store.ActiveRoomStore = (function()
      *
      * @param {String} eventName The name of the event
      * @param {Object} roomData  The new roomData.
      */
     _handleRoomUpdate: function(eventName, roomData) {
       this.dispatchAction(new sharedActions.UpdateRoomInfo({
         urls: roomData.decryptedContext.urls,
         description: roomData.decryptedContext.description,
+        participants: roomData.participants,
         roomName: roomData.decryptedContext.roomName,
         roomUrl: roomData.roomUrl
       }));
     },
 
     /**
      * Handles the deletion of a room, notified by the mozLoop rooms API.
      *
@@ -787,17 +791,26 @@ loop.store.ActiveRoomStore = (function()
     },
 
     /**
      * Handles a remote peer disconnecting from the session. As we currently only
      * support 2 participants, we declare the room as SESSION_CONNECTED as soon as
      * one participantleaves.
      */
     remotePeerDisconnected: function() {
+      // Update the participants to just the owner.
+      var participants = this.getStoreState("participants");
+      if (participants) {
+        participants = participants.filter(function(participant) {
+          return participant.owner;
+        });
+      }
+
       this.setStoreState({
+        participants: participants,
         roomState: ROOM_STATES.SESSION_CONNECTED,
         remoteSrcVideoObject: null
       });
     },
 
     /**
      * Handles an SDK status update, forwarding it to the server.
      *
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -465,28 +465,73 @@ describe("loop.roomViews", function () {
         });
 
       it("should render the DesktopRoomInvitationView if roomState is `JOINED`",
         function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
 
           view = mountTestComponent();
 
+          expect(TestUtils.findRenderedComponentWithType(view,
+            loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.not.eql(null);
+        });
+
+      it("should render the DesktopRoomInvitationView if roomState is `JOINED` with just owner",
+        function() {
+          activeRoomStore.setStoreState({
+            participants: [{owner: true}],
+            roomState: ROOM_STATES.JOINED
+          });
+
+          view = mountTestComponent();
+
+          expect(TestUtils.findRenderedComponentWithType(view,
+            loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.not.eql(null);
+        });
+
+      it("should render the DesktopRoomConversationView if roomState is `JOINED` with remote participant",
+        function() {
+          activeRoomStore.setStoreState({
+            participants: [{}],
+            roomState: ROOM_STATES.JOINED
+          });
+
+          view = mountTestComponent();
+
           TestUtils.findRenderedComponentWithType(view,
-            loop.roomViews.DesktopRoomInvitationView);
+            loop.roomViews.DesktopRoomConversationView);
+          expect(TestUtils.findRenderedComponentWithType(view,
+            loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
+        });
+
+      it("should render the DesktopRoomConversationView if roomState is `JOINED` with participants",
+        function() {
+          activeRoomStore.setStoreState({
+            participants: [{owner: true}, {}],
+            roomState: ROOM_STATES.JOINED
+          });
+
+          view = mountTestComponent();
+
+          TestUtils.findRenderedComponentWithType(view,
+            loop.roomViews.DesktopRoomConversationView);
+          expect(TestUtils.findRenderedComponentWithType(view,
+            loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
         });
 
       it("should render the DesktopRoomConversationView if roomState is `HAS_PARTICIPANTS`",
         function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
 
           view = mountTestComponent();
 
           TestUtils.findRenderedComponentWithType(view,
             loop.roomViews.DesktopRoomConversationView);
+          expect(TestUtils.findRenderedComponentWithType(view,
+            loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
         });
 
       it("should call onCallTerminated when the call ended", function() {
         activeRoomStore.setStoreState({
           roomState: ROOM_STATES.ENDED,
           used: true
         });
 
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -305,16 +305,17 @@ describe("loop.store.ActiveRoomStore", f
     var fakeToken, fakeRoomData;
 
     beforeEach(function() {
       fakeToken = "337-ff-54";
       fakeRoomData = {
         decryptedContext: {
           roomName: "Monkeys"
         },
+        participants: [],
         roomUrl: "http://invalid"
       };
 
       store = new loop.store.ActiveRoomStore(dispatcher, {
         mozLoop: fakeMozLoop,
         sdkDriver: {}
       });
       fakeMozLoop.rooms.get.withArgs(fakeToken).callsArgOnWith(
@@ -345,16 +346,17 @@ describe("loop.store.ActiveRoomStore", f
           roomToken: fakeToken
         }));
 
         sinon.assert.calledTwice(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.SetupRoomInfo({
             roomContextUrls: undefined,
             roomDescription: undefined,
+            participants: [],
             roomToken: fakeToken,
             roomName: fakeRoomData.decryptedContext.roomName,
             roomUrl: fakeRoomData.roomUrl,
             socialShareProviders: []
           }));
       });
 
     it("should dispatch a JoinRoom action if the get is successful",
@@ -1272,16 +1274,40 @@ describe("loop.store.ActiveRoomStore", f
       store.setStoreState({
         remoteSrcVideoObject: { name: "fakeVideoElement" }
       });
 
       store.remotePeerDisconnected();
 
       expect(store.getStoreState().remoteSrcVideoObject).eql(null);
     });
+
+    it("should remove non-owner participants", function() {
+      store.setStoreState({
+        participants: [{owner: true}, {}]
+      });
+
+      store.remotePeerDisconnected();
+
+      var participants = store.getStoreState().participants;
+      expect(participants).to.have.length.of(1);
+      expect(participants[0].owner).eql(true);
+    });
+
+    it("should keep the owner participant", function() {
+      store.setStoreState({
+        participants: [{owner: true}]
+      });
+
+      store.remotePeerDisconnected();
+
+      var participants = store.getStoreState().participants;
+      expect(participants).to.have.length.of(1);
+      expect(participants[0].owner).eql(true);
+    });
   });
 
   describe("#connectionStatus", function() {
     it("should call rooms.sendConnectionStatus on mozLoop", function() {
       store.setStoreState({
         roomToken: "fakeToken",
         sessionToken: "9876543210"
       });
@@ -1513,16 +1539,17 @@ describe("loop.store.ActiveRoomStore", f
         };
 
         fakeMozLoop.rooms.on.callArgWith(1, "update", fakeRoomData);
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.UpdateRoomInfo({
             description: "fakeDescription",
+            participants: undefined,
             roomName: fakeRoomData.decryptedContext.roomName,
             roomUrl: fakeRoomData.roomUrl,
             urls: {
               fake: "url"
             }
           }));
       });