Follow-up to bug 1079225 - Fix formatting of the waiting for media message in Loop rooms, and ensure feedback can be given for multiple conversations in a row. r=abr
authorMark Banner <standard8@mozilla.com>
Wed, 26 Nov 2014 21:09:09 +0000
changeset 217532 ce3eeca4f793
parent 217531 5c804aa57e4b
child 217533 96f7ccef47ad
push id10177
push usermbanner@mozilla.com
push dateWed, 26 Nov 2014 21:09:29 +0000
treeherderfx-team@ce3eeca4f793 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersabr
bugs1079225
milestone36.0a1
Follow-up to bug 1079225 - Fix formatting of the waiting for media message in Loop rooms, and ensure feedback can be given for multiple conversations in a row. r=abr
browser/components/loop/content/shared/js/actions.js
browser/components/loop/content/shared/js/activeRoomStore.js
browser/components/loop/content/shared/js/feedbackStore.js
browser/components/loop/content/shared/js/store.js
browser/components/loop/standalone/content/js/standaloneRoomViews.js
browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
browser/components/loop/standalone/content/js/webapp.js
browser/components/loop/standalone/content/js/webapp.jsx
browser/components/loop/test/shared/activeRoomStore_test.js
browser/components/loop/test/shared/feedbackStore_test.js
browser/components/loop/test/standalone/standaloneRoomViews_test.js
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -326,19 +326,20 @@ loop.shared.actions = (function() {
     JoinedRoom: Action.define("joinedRoom", {
       apiKey: String,
       sessionToken: String,
       sessionId: String,
       expires: Number
     }),
 
     /**
-     * Resets current room.
+     * Used to indicate that the feedback cycle is completed and the countdown
+     * finished.
      */
-    ResetRoom: Action.define("resetRoom", {
+    FeedbackComplete: Action.define("feedbackComplete", {
     }),
 
     /**
      * Used to indicate the user wishes to leave the room.
      */
     LeaveRoom: Action.define("leaveRoom", {
     }),
 
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -135,17 +135,17 @@ loop.store.ActiveRoomStore = (function()
         "joinedRoom",
         "connectedToSdkServers",
         "connectionFailure",
         "setMute",
         "remotePeerDisconnected",
         "remotePeerConnected",
         "windowUnload",
         "leaveRoom",
-        "resetRoom"
+        "feedbackComplete"
       ]);
     },
 
     /**
      * Execute setupWindowData event action from the dispatcher. This gets
      * the room data from the mozLoop api, and dispatches an UpdateRoomInfo event.
      * It also dispatches JoinRoom as this action is only applicable to the desktop
      * client, and needs to auto-join.
@@ -448,17 +448,19 @@ loop.store.ActiveRoomStore = (function()
         this._mozLoop.rooms.leave(this._storeState.roomToken,
           this._storeState.sessionToken);
       }
 
       this.setStoreState({roomState: nextState || ROOM_STATES.ENDED});
     },
 
     /**
-     * Resets current room.
+     * When feedback is complete, we reset the room to the initial state.
      */
-    resetRoom: function() {
+    feedbackComplete: function() {
+      // Note, that we want some values, such as the windowId, so we don't
+      // do a full reset here.
       this.setStoreState(this.getInitialStoreState());
     }
   });
 
   return ActiveRoomStore;
 })();
--- a/browser/components/loop/content/shared/js/feedbackStore.js
+++ b/browser/components/loop/content/shared/js/feedbackStore.js
@@ -32,17 +32,18 @@ loop.store.FeedbackStore = (function() {
    * @param {Object} options Options object:
    * - {mozLoop}        mozLoop                 The MozLoop API object.
    * - {feedbackClient} loop.FeedbackAPIClient  The feedback API client.
    */
   var FeedbackStore = loop.store.createStore({
     actions: [
       "requireFeedbackDetails",
       "sendFeedback",
-      "sendFeedbackError"
+      "sendFeedbackError",
+      "feedbackComplete"
     ],
 
     initialize: function(options) {
       if (!options.feedbackClient) {
         throw new Error("Missing option feedbackClient");
       }
       this._feedbackClient = options.feedbackClient;
     },
@@ -86,13 +87,21 @@ loop.store.FeedbackStore = (function() {
      *
      * @param {sharedActions.SendFeedback} actionData The action data.
      */
     sendFeedbackError: function(actionData) {
       this.setStoreState({
         feedbackState: FEEDBACK_STATES.FAILED,
         error: actionData.error
       });
+    },
+
+    /**
+     * Resets the store to its initial state as feedback has been completed,
+     * i.e. ready for the next round of feedback.
+     */
+    feedbackComplete: function() {
+      this.resetStoreState();
     }
   });
 
   return FeedbackStore;
 })();
--- a/browser/components/loop/content/shared/js/store.js
+++ b/browser/components/loop/content/shared/js/store.js
@@ -49,16 +49,27 @@ loop.store.createStore = (function() {
      * @param {Object} newState The new store state object.
      */
     setStoreState: function(newState) {
       for (var key in newState) {
         this._storeState[key] = newState[key];
         this.trigger("change:" + key);
       }
       this.trigger("change");
+    },
+
+    /**
+     * Resets the store state to the initially defined state.
+     */
+    resetStoreState: function() {
+      if (typeof this.getInitialStoreState === "function") {
+        this._storeState = this.getInitialStoreState();
+      } else {
+        this._storeState = {};
+      }
     }
   };
 
   /**
    * Creates a new Store constructor.
    *
    * @param  {Object}   storeProto The store prototype.
    * @return {Function}            A store constructor.
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -24,17 +24,17 @@ loop.standaloneRoomViews = (function(moz
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       feedbackStore:
         React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
     },
 
     onFeedbackSent: function() {
       // We pass a tick to prevent React warnings regarding nested updates.
       setTimeout(function() {
-        this.props.activeRoomStore.dispatchAction(new sharedActions.ResetRoom());
+        this.props.activeRoomStore.dispatchAction(new sharedActions.FeedbackComplete());
       }.bind(this));
     },
 
     _renderCallToActionLink: function() {
       if (this.props.helper.isFirefox(navigator.userAgent)) {
         return (
           React.DOM.a({href: loop.config.learnMoreUrl, className: "btn btn-info"}, 
             mozL10n.get("rooms_room_full_call_to_action_label", {
@@ -80,18 +80,20 @@ loop.standaloneRoomViews = (function(moz
             )
           );
         }
         case ROOM_STATES.MEDIA_WAIT: {
           var msg = mozL10n.get("call_progress_getting_media_description",
                                 {clientShortname: mozL10n.get("clientShortname2")});
           // XXX Bug 1047040 will add images to help prompt the user.
           return (
-            React.DOM.p({className: "prompt-media-message"}, 
-              msg
+            React.DOM.div({className: "room-inner-info-area"}, 
+              React.DOM.p({className: "prompt-media-message"}, 
+                msg
+              )
             )
           );
         }
         case ROOM_STATES.JOINED:
         case ROOM_STATES.SESSION_CONNECTED: {
           return (
             React.DOM.div({className: "room-inner-info-area"}, 
               React.DOM.p({className: "empty-room-message"}, 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -24,17 +24,17 @@ loop.standaloneRoomViews = (function(moz
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       feedbackStore:
         React.PropTypes.instanceOf(loop.store.FeedbackStore).isRequired
     },
 
     onFeedbackSent: function() {
       // We pass a tick to prevent React warnings regarding nested updates.
       setTimeout(function() {
-        this.props.activeRoomStore.dispatchAction(new sharedActions.ResetRoom());
+        this.props.activeRoomStore.dispatchAction(new sharedActions.FeedbackComplete());
       }.bind(this));
     },
 
     _renderCallToActionLink: function() {
       if (this.props.helper.isFirefox(navigator.userAgent)) {
         return (
           <a href={loop.config.learnMoreUrl} className="btn btn-info">
             {mozL10n.get("rooms_room_full_call_to_action_label", {
@@ -80,19 +80,21 @@ loop.standaloneRoomViews = (function(moz
             </div>
           );
         }
         case ROOM_STATES.MEDIA_WAIT: {
           var msg = mozL10n.get("call_progress_getting_media_description",
                                 {clientShortname: mozL10n.get("clientShortname2")});
           // XXX Bug 1047040 will add images to help prompt the user.
           return (
-            <p className="prompt-media-message">
-              {msg}
-            </p>
+            <div className="room-inner-info-area">
+              <p className="prompt-media-message">
+                {msg}
+              </p>
+            </div>
           );
         }
         case ROOM_STATES.JOINED:
         case ROOM_STATES.SESSION_CONNECTED: {
           return (
             <div className="room-inner-info-area">
               <p className="empty-room-message">
                 {mozL10n.get("rooms_only_occupant_label")}
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -685,19 +685,20 @@ loop.webapp = (function($, _, OT, mozL10
       this.props.conversation.off(null, null, this);
     },
 
     shouldComponentUpdate: function(nextProps, nextState) {
       // Only rerender if current state has actually changed
       return nextState.callStatus !== this.state.callStatus;
     },
 
-    callStatusSwitcher: function(status) {
+    resetCallStatus: function() {
+      this.props.feedbackStore.dispatchAction(new sharedActions.FeedbackComplete());
       return function() {
-        this.setState({callStatus: status});
+        this.setState({callStatus: "start"});
       }.bind(this);
     },
 
     /**
      * Renders the conversation views.
      */
     render: function() {
       switch (this.state.callStatus) {
@@ -739,17 +740,17 @@ loop.webapp = (function($, _, OT, mozL10
           );
         }
         case "end": {
           return (
             EndedConversationView({
               sdk: this.props.sdk, 
               conversation: this.props.conversation, 
               feedbackStore: this.props.feedbackStore, 
-              onAfterFeedbackReceived: this.callStatusSwitcher("start")}
+              onAfterFeedbackReceived: this.resetCallStatus()}
             )
           );
         }
         case "expired": {
           return (
             CallUrlExpiredView({helper: this.props.helper})
           );
         }
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -685,19 +685,20 @@ loop.webapp = (function($, _, OT, mozL10
       this.props.conversation.off(null, null, this);
     },
 
     shouldComponentUpdate: function(nextProps, nextState) {
       // Only rerender if current state has actually changed
       return nextState.callStatus !== this.state.callStatus;
     },
 
-    callStatusSwitcher: function(status) {
+    resetCallStatus: function() {
+      this.props.feedbackStore.dispatchAction(new sharedActions.FeedbackComplete());
       return function() {
-        this.setState({callStatus: status});
+        this.setState({callStatus: "start"});
       }.bind(this);
     },
 
     /**
      * Renders the conversation views.
      */
     render: function() {
       switch (this.state.callStatus) {
@@ -739,17 +740,17 @@ loop.webapp = (function($, _, OT, mozL10
           );
         }
         case "end": {
           return (
             <EndedConversationView
               sdk={this.props.sdk}
               conversation={this.props.conversation}
               feedbackStore={this.props.feedbackStore}
-              onAfterFeedbackReceived={this.callStatusSwitcher("start")}
+              onAfterFeedbackReceived={this.resetCallStatus()}
             />
           );
         }
         case "expired": {
           return (
             <CallUrlExpiredView helper={this.props.helper} />
           );
         }
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -259,27 +259,27 @@ describe("loop.store.ActiveRoomStore", f
         windowType: "room",
         token: "fakeToken"
       }));
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
     });
   });
 
-  describe("#resetRoom", function() {
+  describe("#feedbackComplete", function() {
     it("should reset the room store state", function() {
       var initialState = store.getInitialStoreState();
       store.setStoreState({
         roomState: ROOM_STATES.ENDED,
         audioMuted: true,
         videoMuted: true,
         failureReason: "foo"
       });
 
-      store.resetRoom(new sharedActions.ResetRoom());
+      store.feedbackComplete(new sharedActions.FeedbackComplete());
 
       expect(store.getStoreState()).eql(initialState);
     });
   });
 
   describe("#setupRoomInfo", function() {
     var fakeRoomInfo;
 
--- a/browser/components/loop/test/shared/feedbackStore_test.js
+++ b/browser/components/loop/test/shared/feedbackStore_test.js
@@ -100,9 +100,21 @@ describe("loop.store.FeedbackStore", fun
       store.once("change:feedbackState", function() {
         expect(store.getStoreState("feedbackState")).eql(FEEDBACK_STATES.FAILED);
         done();
       });
 
       store.sendFeedback(new sharedActions.SendFeedback(sadFeedbackData));
     });
   });
+
+  describe("feedbackComplete", function() {
+    it("should reset the store state", function() {
+      store.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
+
+      store.feedbackComplete();
+
+      expect(store.getStoreState()).eql({
+        feedbackState: FEEDBACK_STATES.INIT
+      });
+    });
+  });
 });
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -286,27 +286,31 @@ describe("loop.standaloneRoomViews", fun
           TestUtils.Simulate.click(getLeaveButton(view));
 
           sinon.assert.calledOnce(dispatch);
           sinon.assert.calledWithExactly(dispatch, new sharedActions.LeaveRoom());
         });
       });
 
       describe("Feedback", function() {
+        beforeEach(function() {
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
+        });
+
         it("should display a feedback form when the user leaves the room",
           function() {
-            activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
-
             expect(view.getDOMNode().querySelector(".faces")).not.eql(null);
           });
 
-        it("should reinit the view after feedback is sent", function() {
-          feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
+        it("should dispatch a `FeedbackComplete` action after feedback is sent",
+          function() {
+            feedbackStore.setStoreState({feedbackState: FEEDBACK_STATES.SENT});
 
-          sandbox.clock.tick(
-            loop.shared.views.WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS * 1000);
+            sandbox.clock.tick(
+              loop.shared.views.WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS * 1000 + 1000);
 
-          expect(view.getDOMNode().querySelector(".btn-join")).not.eql(null);
-        });
+            sinon.assert.calledOnce(dispatch);
+            sinon.assert.calledWithExactly(dispatch, new sharedActions.FeedbackComplete());
+          });
       });
     });
   });
 });