Bug 1131542 - Loop button on toolbar needs different tooltips to explain colours/state. r=Standard8
authorManuel Casas <manuel.casasbarrado@gmail.com>
Fri, 16 Oct 2015 17:06:48 +0100
changeset 268009 e3d9d9c375846ba6874662b73aed0ffe20ca503c
parent 268008 463e8512cb445e3849840b9cfb894b663dbd4f9f
child 268010 5bf43b7a4e7bdc301c1c0df8c4c839e035ac6aae
push id15722
push usermbanner@mozilla.com
push dateFri, 16 Oct 2015 16:07:14 +0000
treeherderfx-team@e3d9d9c37584 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1131542
milestone44.0a1
Bug 1131542 - Loop button on toolbar needs different tooltips to explain colours/state. r=Standard8
browser/base/content/browser-loop.js
browser/components/loop/modules/LoopRooms.jsm
browser/components/loop/test/mochitest/.eslintrc
browser/components/loop/test/mochitest/browser_toolbarbutton.js
browser/components/uitour/test/browser_UITour_loop.js
browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -271,28 +271,58 @@ var LoopUI;
      *                   successfully. This is used so the state can be
      *                   temporarily shown until the next state change.
      */
     updateToolbarState: function(aReason = null) {
       if (!this.toolbarButton.node) {
         return;
       }
       let state = "";
+      let mozL10nId = "loop-call-button3";
+      let suffix = ".tooltiptext";
       if (this.MozLoopService.errors.size) {
         state = "error";
+        mozL10nId += "-error";
       } else if (this.MozLoopService.screenShareActive) {
         state = "action";
+        mozL10nId += "-screensharing";
       } else if (aReason == "login" && this.MozLoopService.userProfile) {
         state = "active";
+        mozL10nId += "-active";
       } else if (this.MozLoopService.doNotDisturb) {
         state = "disabled";
+        mozL10nId += "-donotdisturb";
       } else if (this.MozLoopService.roomsParticipantsCount > 0) {
         state = "active";
+        this.roomsWithNonOwners().then(roomsWithNonOwners => {
+          if (roomsWithNonOwners.length > 0) {
+            mozL10nId += "-participantswaiting";
+          } else {
+            mozL10nId += "-active";
+          }
+
+          this.updateTooltiptext(mozL10nId + suffix);
+          this.toolbarButton.node.setAttribute("state", state);
+        });
+        return;
       }
       this.toolbarButton.node.setAttribute("state", state);
+      this.updateTooltiptext(mozL10nId + suffix);
+    },
+
+    /**
+     * Updates the tootltiptext to reflect Loop status.
+     *
+     * @param {string} [mozL10nId] l10n ID that refelct the current
+     *                           Loop status.
+     */
+    updateTooltiptext: function(mozL10nId) {
+      this.toolbarButton.node.setAttribute("tooltiptext", mozL10nId);
+      var tooltiptext = CustomizableUI.getLocalizedProperty(this.toolbarButton, "tooltiptext");
+      this.toolbarButton.node.setAttribute("tooltiptext", tooltiptext);
     },
 
     /**
      * Show a desktop notification when 'do not disturb' isn't enabled.
      *
      * @param {Object} options Set of options that may tweak the appearance and
      *                         behavior of the notification.
      *                         Option params:
--- a/browser/components/loop/modules/LoopRooms.jsm
+++ b/browser/components/loop/modules/LoopRooms.jsm
@@ -1111,18 +1111,20 @@ this.LoopRooms = {
    * Expose the internal rooms map for testing purposes only. This avoids
    * needing to mock the server interfaces.
    *
    * @param {Map} roomsCache The new cache data to set for testing purposes. If
    *                         not specified, it will reset the cache.
    */
   _setRoomsCache: function(roomsCache) {
     LoopRoomsInternal.rooms.clear();
+    gDirty = true;
 
     if (roomsCache) {
       // Need a clone as the internal map is read-only.
       for (let [key, value] of roomsCache) {
         LoopRoomsInternal.rooms.set(key, value);
       }
+      gDirty = false;
     }
   }
 };
 Object.freeze(this.LoopRooms);
--- a/browser/components/loop/test/mochitest/.eslintrc
+++ b/browser/components/loop/test/mochitest/.eslintrc
@@ -29,11 +29,12 @@
     "promiseWaitForCondition": false,
     "resetFxA": true,
     // Loop specific items
     "MozLoopServiceInternal": true,
     "LoopRoomsInternal": true,
     "LoopUI": false,
     // Other items
     "Chat": true,
-    "WebChannel": true
+    "WebChannel": true,
+    "executeSoon": true
   }
 }
--- a/browser/components/loop/test/mochitest/browser_toolbarbutton.js
+++ b/browser/components/loop/test/mochitest/browser_toolbarbutton.js
@@ -38,85 +38,122 @@ add_task(function* test_LoopUI_getters()
   Assert.ok(LoopUI.browser, "Browser element should be there");
 
   // Hide the panel.
   yield LoopUI.togglePanel();
 });
 
 add_task(function* test_doNotDisturb() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   yield MozLoopService.doNotDisturb = true;
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Do not disturb", "Check button has disabled tooltiptext");
   yield MozLoopService.doNotDisturb = false;
   Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is not in disabled state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
 });
 
 add_task(function* test_doNotDisturb_with_login() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   yield MozLoopService.doNotDisturb = true;
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Do not disturb", "Check button has disabled tooltiptext");
   MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken;
   MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile;
   yield MozLoopServiceInternal.notifyStatusChanged("login");
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Active conversation", "Check button has active tooltiptext");
   yield loadLoopPanel();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state after opening panel");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Do not disturb", "Check button has disabled tooltiptext");
   LoopUI.panel.hidePopup();
   yield MozLoopService.doNotDisturb = false;
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   MozLoopServiceInternal.fxAOAuthTokenData = null;
   yield MozLoopServiceInternal.notifyStatusChanged();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
 });
 
 add_task(function* test_error() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   yield MozLoopServiceInternal.setError("testing", {});
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Error!", "Check button has error tooltiptext");
   yield MozLoopServiceInternal.clearError("testing");
   Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is not in error state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
 });
 
 add_task(function* test_error_with_login() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   yield MozLoopServiceInternal.setError("testing", {});
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Error!", "Check button has error tooltiptext");
   MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile;
   MozLoopServiceInternal.notifyStatusChanged("login");
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Error!", "Check button has error tooltiptext");
   yield MozLoopServiceInternal.clearError("testing");
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   MozLoopServiceInternal.fxAOAuthProfile = null;
   MozLoopServiceInternal.notifyStatusChanged();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
 });
 
 add_task(function* test_active() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken;
   MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile;
   yield MozLoopServiceInternal.notifyStatusChanged("login");
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Active conversation", "Check button has active tooltiptext");
   yield loadLoopPanel();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state after opening panel");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   LoopUI.panel.hidePopup();
   MozLoopServiceInternal.fxAOAuthTokenData = null;
   MozLoopServiceInternal.notifyStatusChanged();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
 });
 
 add_task(function* test_room_participants() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
-  LoopRoomsInternal.rooms.set("test_room", {participants: [{displayName: "hugh", id: "008"}]});
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
+  let roomsCache = new Map([[ "test_room", {participants: [{displayName: "hugh", id: "008", owner: true}]} ]]);
+  LoopRooms._setRoomsCache(roomsCache);
   MozLoopServiceInternal.notifyStatusChanged();
+  // Since we're changing the rooms map directly, we're expecting it to be a synchronous operation.
+  // But Promises have the inherent property of then-ables being async so even though the operation returns immediately,
+  // because the cache is hit, the promise won't be resolved until after the next tick.
+  // And that's what the line below does, waits until the next tick
+  yield new Promise(resolve => executeSoon(resolve));
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
-  LoopRoomsInternal.rooms.set("test_room", {participants: []});
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Active conversation", "Check button has active tooltiptext");
+  roomsCache.set("test_room", {participants: [{displayName: "hugh", id: "008", owner: false}]});
+  LoopRooms._setRoomsCache(roomsCache);
+  MozLoopServiceInternal.notifyStatusChanged();
+  yield new Promise(resolve => executeSoon(resolve));
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Someone is waiting for you in a conversation", "Check button has participantswaiting tooltiptext");
+  roomsCache.set("test_room", {participants: []});
+  LoopRooms._setRoomsCache(roomsCache);
   MozLoopServiceInternal.notifyStatusChanged();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
-  LoopRoomsInternal.rooms.delete("test_room");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
+  LoopRooms._setRoomsCache();
 });
 
 add_task(function* test_panelToggle_on_click() {
   // Since we _know_ the first click on the button will open the panel, we'll
   // open it using the test utility and check the correct state by clicking the
   // button. This should hide the panel.
   // If we'd open the panel with a simulated click on the button, we won't know
   // for sure when the panel has opened, because the event loop spins a few times
@@ -125,20 +162,23 @@ add_task(function* test_panelToggle_on_c
   Assert.strictEqual(LoopUI.panel.state, "open", "Panel should be open");
   // The panel should now be visible. Clicking the button should hide it.
   LoopUI.toolbarButton.node.click();
   Assert.strictEqual(LoopUI.panel.state, "closed", "Panel should be closed");
 });
 
 add_task(function* test_screen_share() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
   MozLoopService.setScreenShareState("1", true);
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "action", "Check button is in action state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "You are sharing your screen", "Check button has sharingscreen tooltiptext");
   MozLoopService.setScreenShareState("1", false);
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
 });
 
 add_task(function* test_private_browsing_window() {
   let win = OpenBrowserWindow({ private: true });
   yield new Promise(resolve => {
     win.addEventListener("load", function listener() {
       win.removeEventListener("load", listener);
       resolve();
--- a/browser/components/uitour/test/browser_UITour_loop.js
+++ b/browser/components/uitour/test/browser_UITour_loop.js
@@ -246,16 +246,17 @@ var tests = [
         chatWin.navigator.wrappedJSObject.mozLoop.composeEmail = function(recipient, subject, body) {
           ok(recipient, "composeEmail should be invoked with at least a recipient value");
           composeEmailCalled = true;
           chatWin.navigator.wrappedJSObject.mozLoop.composeEmail = oldComposeEmail;
         };
         chatWin.document.querySelector(".btn-email").click();
       });
     });
+    setupFakeRoom();
     LoopRooms.open("fakeTourRoom");
   }),
   taskify(function* test_arrow_panel_position() {
     is(loopButton.open, false, "Menu should initially be closed");
     let popup = document.getElementById("UITourTooltip");
 
     yield showMenuPromise("loop");
 
@@ -344,16 +345,17 @@ function checkLoopPanelIsHidden() {
   is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
 }
 
 function setupFakeRoom() {
   let room = {};
   for (let prop of ["roomToken", "roomOwner", "roomUrl", "participants"])
     room[prop] = "fakeTourRoom";
   room.decryptedContext = {roomName: "fakeTourRoom"};
+  room.participants = [];
   let roomsMap = new Map([
     [room.roomToken, room]
   ]);
   LoopRooms.stubCache(roomsMap);
   return roomsMap;
 }
 
 if (Services.prefs.getBoolPref("loop.enabled")) {
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -92,16 +92,21 @@ quit-button.tooltiptext.linux2 = Quit %1
 # LOCALIZATION NOTE(quit-button.tooltiptext.mac): %1$S is the brand name (e.g. Firefox),
 # %2$S is the keyboard shortcut
 quit-button.tooltiptext.mac = Quit %1$S (%2$S)
 
 # LOCALIZATION NOTE(loop-call-button3.label): This is a brand name, request
 # approval before you change it.
 loop-call-button3.label = Hello
 loop-call-button3.tooltiptext = Start a conversation
+loop-call-button3-error.tooltiptext = Error!
+loop-call-button3-donotdisturb.tooltiptext = Do not disturb
+loop-call-button3-screensharing.tooltiptext = You are sharing your screen
+loop-call-button3-active.tooltiptext = Active conversation
+loop-call-button3-participantswaiting.tooltiptext = Someone is waiting for you in a conversation
 # LOCALIZATION NOTE(loop-call-button3-pb.tooltiptext): Shown when the button is
 # placed inside a Private Browsing window. %S is the value of loop-call-button3.label.
 loop-call-button3-pb.tooltiptext = %S is not available in Private Browsing
 
 social-share-button.label = Share This Page
 social-share-button.tooltiptext = Share this page
 
 panic-button.label = Forget