Merge "Revert "Add params to change-view-integration plugin endpoint""
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index a935e26..0de1580 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -372,6 +372,10 @@
           detail: Gerrit.Nav.GroupDetailView.MEMBERS,
         });
       },
+
+      getUrlForSettings() {
+        return this._getUrlFor({view: Gerrit.Nav.View.SETTINGS});
+      },
     };
   })(window);
 </script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 8bc2a0b..c31855a 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -228,6 +228,8 @@
         url = this._generateDiffOrEditUrl(params);
       } else if (params.view === Views.GROUP) {
         url = this._generateGroupUrl(params);
+      } else if (params.view === Views.SETTINGS) {
+        url = this._generateSettingsUrl(params);
       } else {
         throw new Error('Can\'t generate');
       }
@@ -352,6 +354,14 @@
     },
 
     /**
+     * @param {!Object} params
+     * @return {string}
+     */
+    _generateSettingsUrl(params) {
+      return '/settings';
+    },
+
+    /**
      * Given an object of parameters, potentially including a `patchNum` or a
      * `basePatchNum` or both, return a string representation of that range. If
      * no range is indicated in the params, the empty string is returned.
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
index 2490509..03380e4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -251,6 +251,22 @@
       };
     },
 
+    /**
+     * The only line in which add a comment tooltip is cut off is the first
+     * line. Even if there is a collapsed section, The first visible line is
+     * in the position where the second line would have been, if not for the
+     * collapsed section, so don't need to worry about this case for
+     * positioning the tooltip.
+     */
+    _positionActionBox(actionBox, startLine, range) {
+      if (startLine > 1) {
+        actionBox.placeAbove(range);
+        return;
+      }
+      actionBox.positionBelow = true;
+      actionBox.placeBelow(range);
+    },
+
     _handleSelection() {
       const normalizedRange = this._getNormalizedRange();
       if (!normalizedRange) {
@@ -285,17 +301,18 @@
       };
       actionBox.side = start.side;
       if (start.line === end.line) {
-        actionBox.placeAbove(domRange);
+        this._positionActionBox(actionBox, start.line, domRange);
       } else if (start.node instanceof Text) {
         if (start.column) {
-          actionBox.placeAbove(start.node.splitText(start.column));
+          this._positionActionBox(actionBox, start.line,
+              start.node.splitText(start.column));
         }
         start.node.parentElement.normalize(); // Undo splitText from above.
       } else if (start.node.classList.contains('content') &&
-                 start.node.firstChild) {
-        actionBox.placeAbove(start.node.firstChild);
+          start.node.firstChild) {
+        this._positionActionBox(actionBox, start.line, start.node.firstChild);
       } else {
-        actionBox.placeAbove(start.node);
+        this._positionActionBox(actionBox, start.line, start.node);
       }
     },
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index b63b9a4..4bbf12b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -38,6 +38,27 @@
       <table id="diffTable">
 
         <tbody class="section both">
+           <tr class="diff-row side-by-side" left-type="both" right-type="both">
+            <td class="left lineNum" data-value="1"></td>
+            <td class="content both"><div class="contentText">[1] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
+            <td class="right lineNum" data-value="1"></td>
+            <td class="content both"><div class="contentText">[1] Nam cum ad me in Cumanum salutandi causa uterque</div></td>
+          </tr>
+        </tbody>
+
+        <tbody class="section delta">
+          <tr class="diff-row side-by-side" left-type="remove" right-type="add">
+            <td class="left lineNum" data-value="2"></td>
+            <!-- Next tag is formatted to eliminate zero-length text nodes. -->
+            <td class="content remove"><div class="contentText">na💢ti <hl class="foo">te, inquit</hl>, sumus <hl class="bar">aliquando</hl> otiosum, <hl>certe</hl> a <hl><span class="tab-indicator" style="tab-size:8;">	</span></hl>udiam, <hl>quid</hl> sit, <span class="tab-indicator" style="tab-size:8;">	</span>quod <hl>Epicurum</hl></div></td>
+            <td class="right lineNum" data-value="2"></td>
+            <!-- Next tag is formatted to eliminate zero-length text nodes. -->
+            <td class="content add"><div class="contentText">nacti , <hl>,</hl> sumus <hl><span class="tab-indicator" style="tab-size:8;">	</span></hl> otiosum,  <span class="tab-indicator" style="tab-size:8;">	</span> audiam,  sit, quod</div></td>
+          </tr>
+        </tbody>
+
+
+        <tbody class="section both">
           <tr class="diff-row side-by-side" left-type="both" right-type="both">
             <td class="left lineNum" data-value="138"></td>
             <td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
@@ -253,6 +274,7 @@
         contentStubs = [];
         stub('gr-selection-action-box', {
           placeAbove: sandbox.stub(),
+          placeBelow: sandbox.stub(),
         });
         diff = element.querySelector('#diffTable');
         builder = {
@@ -270,9 +292,29 @@
         window.getSelection().removeAllRanges();
       });
 
+      test('single first line', () => {
+        const content = stubContent(1, 'right');
+        sandbox.spy(element, '_positionActionBox');
+        emulateSelection(content.firstChild, 5, content.firstChild, 12);
+        const actionBox = element.$$('gr-selection-action-box');
+        assert.isTrue(actionBox.positionBelow);
+      });
+
+      test('multiline starting on first line', () => {
+        const startContent = stubContent(1, 'right');
+        const endContent = stubContent(2, 'right');
+        sandbox.spy(element, '_positionActionBox');
+        emulateSelection(
+            startContent.firstChild, 10, endContent.lastChild, 7);
+        const actionBox = element.$$('gr-selection-action-box');
+        assert.isTrue(actionBox.positionBelow);
+      });
+
       test('single line', () => {
         const content = stubContent(138, 'left');
+        sandbox.spy(element, '_positionActionBox');
         emulateSelection(content.firstChild, 5, content.firstChild, 12);
+        const actionBox = element.$$('gr-selection-action-box');
         assert.isTrue(element.isRangeSelected());
         assert.deepEqual(getActionRange(), {
           startLine: 138,
@@ -281,14 +323,18 @@
           endChar: 12,
         });
         assert.equal(getActionSide(), 'left');
+        assert.notOk(actionBox.positionBelow);
       });
 
       test('multiline', () => {
         const startContent = stubContent(119, 'right');
         const endContent = stubContent(120, 'right');
+        sandbox.spy(element, '_positionActionBox');
         emulateSelection(
             startContent.firstChild, 10, endContent.lastChild, 7);
         assert.isTrue(element.isRangeSelected());
+        const actionBox = element.$$('gr-selection-action-box');
+
         assert.deepEqual(getActionRange(), {
           startLine: 119,
           startChar: 10,
@@ -296,6 +342,7 @@
           endChar: 36,
         });
         assert.equal(getActionSide(), 'right');
+        assert.notOk(actionBox.positionBelow);
       });
 
       test('multiple ranges aka firefox implementation', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
index 47db5f0..3993b86 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
@@ -17,34 +17,24 @@
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
 <link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-tooltip/gr-tooltip.html">
 
 <dom-module id="gr-selection-action-box">
   <template>
     <style include="shared-styles">
       :host {
-        --gr-arrow-size: .65em;
-
-        background-color: rgba(22, 22, 22, .9);
-        border-radius: 3px;
-        color: #fff;
         cursor: pointer;
         font-family: var(--font-family);
-        padding: .5em .75em;
         position: absolute;
         white-space: nowrap;
       }
-      .arrow {
-        border: var(--gr-arrow-size) solid transparent;
-        border-top: var(--gr-arrow-size) solid rgba(22, 22, 22, 0.9);
-        height: 0;
-        left: calc(50% - var(--gr-arrow-size));
-        margin-top: .5em;
-        position: absolute;
-        width: 0;
+      #tooltip {
+        --tooltip-background-color: rgba(22, 22, 22, .9);
       }
     </style>
-    Press <strong>c</strong> to comment.
-    <div class="arrow"></div>
+    <gr-tooltip id="tooltip"
+        text="press c to comment"
+        position-below="[[positionBelow]]"></gr-tooltip>
   </template>
   <script src="gr-selection-action-box.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
index c228235..61a6eb2 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
@@ -37,6 +37,7 @@
           endChar: NaN,
         },
       },
+      positionBelow: Boolean,
       side: {
         type: String,
         value: '',
@@ -58,7 +59,7 @@
     placeAbove(el) {
       Polymer.dom.flush();
       const rect = this._getTargetBoundingRect(el);
-      const boxRect = this.getBoundingClientRect();
+      const boxRect = this.$.tooltip.getBoundingClientRect();
       const parentRect = this.parentElement.getBoundingClientRect();
       this.style.top =
           rect.top - parentRect.top - boxRect.height - 6 + 'px';
@@ -66,6 +67,17 @@
           rect.left - parentRect.left + (rect.width - boxRect.width) / 2 + 'px';
     },
 
+    placeBelow(el) {
+      Polymer.dom.flush();
+      const rect = this._getTargetBoundingRect(el);
+      const boxRect = this.$.tooltip.getBoundingClientRect();
+      const parentRect = this.parentElement.getBoundingClientRect();
+      this.style.top =
+          rect.top - parentRect.top + boxRect.height - 6 + 'px';
+      this.style.left =
+          rect.left - parentRect.left + (rect.width - boxRect.width) / 2 + 'px';
+    },
+
     _getTargetBoundingRect(el) {
       let rect;
       if (el instanceof Text) {
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index 8c70772..7fc634c 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -38,15 +38,17 @@
   suite('gr-selection-action-box', () => {
     let container;
     let element;
+    let sandbox;
 
     setup(() => {
       container = fixture('basic');
       element = container.querySelector('gr-selection-action-box');
-      sinon.stub(element, 'fire');
+      sandbox = sinon.sandbox.create();
+      sandbox.stub(element, 'fire');
     });
 
     teardown(() => {
-      element.fire.restore();
+      sandbox.restore();
     });
 
     test('ignores regular keys', () => {
@@ -65,10 +67,10 @@
       setup(() => {
         e = {
           button: 0,
-          preventDefault: sinon.stub(),
-          stopPropagation: sinon.stub(),
+          preventDefault: sandbox.stub(),
+          stopPropagation: sandbox.stub(),
         };
-        sinon.stub(element, '_fireCreateComment');
+        sandbox.stub(element, '_fireCreateComment');
       });
 
       test('event handled if main button', () => {
@@ -107,20 +109,14 @@
 
       setup(() => {
         target = container.querySelector('.target');
-        sinon.stub(container, 'getBoundingClientRect').returns(
+        sandbox.stub(container, 'getBoundingClientRect').returns(
             {top: 1, bottom: 2, left: 3, right: 4, width: 50, height: 6});
-        sinon.stub(element, '_getTargetBoundingRect').returns(
+        sandbox.stub(element, '_getTargetBoundingRect').returns(
             {top: 42, bottom: 20, left: 30, right: 40, width: 100, height: 60});
-        sinon.stub(element, 'getBoundingClientRect').returns(
+        sandbox.stub(element.$.tooltip, 'getBoundingClientRect').returns(
             {width: 10, height: 10});
       });
 
-      teardown(() => {
-        element.getBoundingClientRect.restore();
-        container.getBoundingClientRect.restore();
-        element._getTargetBoundingRect.restore();
-      });
-
       test('placeAbove for Element argument', () => {
         element.placeAbove(target);
         assert.equal(element.style.top, '25px');
@@ -133,13 +129,24 @@
         assert.equal(element.style.left, '72px');
       });
 
+      test('placeBelow for Element argument', () => {
+        element.placeBelow(target);
+        assert.equal(element.style.top, '45px');
+        assert.equal(element.style.left, '72px');
+      });
+
+      test('placeBelow for Text Node argument', () => {
+        element.placeBelow(target.firstChild);
+        assert.equal(element.style.top, '45px');
+        assert.equal(element.style.left, '72px');
+      });
+
       test('uses document.createRange', () => {
-        sinon.spy(document, 'createRange');
+        sandbox.spy(document, 'createRange');
         element._getTargetBoundingRect.restore();
-        sinon.spy(element, '_getTargetBoundingRect');
+        sandbox.spy(element, '_getTargetBoundingRect');
         element.placeAbove(target.firstChild);
         assert.isTrue(document.createRange.called);
-        document.createRange.restore();
       });
     });
   });
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
index 543c86d..26a2470 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
@@ -24,7 +24,7 @@
     },
 
     _handleMoveUpButton(e) {
-      const index = e.target.dataIndex;
+      const index = Polymer.dom(e).localTarget.dataIndex;
       if (index === 0) { return; }
       const row = this.menuItems[index];
       const prev = this.menuItems[index - 1];
@@ -32,7 +32,7 @@
     },
 
     _handleMoveDownButton(e) {
-      const index = e.target.dataIndex;
+      const index = Polymer.dom(e).localTarget.dataIndex;
       if (index === this.menuItems.length - 1) { return; }
       const row = this.menuItems[index];
       const next = this.menuItems[index + 1];
@@ -40,7 +40,7 @@
     },
 
     _handleDeleteButton(e) {
-      const index = e.target.dataIndex;
+      const index = Polymer.dom(e).localTarget.dataIndex;
       this.splice('menuItems', index, 1);
     },
 
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
index f16ba6c..c70ae88 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
@@ -47,9 +47,10 @@
     // Click the up/down button (according to direction) for the index'th row.
     // The index of the first row is 0, corresponding to the array.
     function move(element, index, direction) {
-      const selector =
-          'tr:nth-child(' + (index + 1) + ') .move' + direction + 'Button';
-      const button = element.$$('tbody').querySelector(selector);
+      const selector = 'tr:nth-child(' + (index + 1) + ') .move' +
+          direction + 'Button';
+      const button =
+          element.$$('tbody').querySelector(selector).$$('paper-button');
       MockInteractions.tap(button);
     }
 
@@ -141,15 +142,15 @@
           ['first name', 'second name', 'third name']);
 
       // Tap the delete button for the middle item.
-      MockInteractions.tap(
-          element.$$('tbody').querySelector('tr:nth-child(2) .remove-button'));
+      MockInteractions.tap(element.$$('tbody')
+          .querySelector('tr:nth-child(2) .remove-button').$$('paper-button'));
 
       assertMenuNamesEqual(element, ['first name', 'third name']);
 
       // Delete remaining items.
       for (let i = 0; i < 2; i++) {
-        MockInteractions.tap(
-            element.$$('tbody').querySelector('tr:first-child .remove-button'));
+        MockInteractions.tap(element.$$('tbody')
+            .querySelector('tr:first-child .remove-button').$$('paper-button'));
       }
       assertMenuNamesEqual(element, []);
 
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
index de6783d..0cbd1f6 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
@@ -16,6 +16,7 @@
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../../../styles/shared-styles.html">
@@ -37,18 +38,23 @@
       header {
         border-bottom: 1px solid #cdcdcd;
         font-family: var(--font-family-bold);
+        margin-bottom: 1em;
       }
-      header,
-      main,
-      footer {
+      .container {
         padding: .5em 1.5em;
       }
       footer {
         display: flex;
-        justify-content: space-between;
+        justify-content: flex-end;
+      }
+      footer gr-button {
+        margin-left: 1em;
+      }
+      input {
+        width: 20em;
       }
     </style>
-    <main class="gr-form-styles">
+    <div class="container gr-form-styles">
       <header>Please confirm your contact information</header>
       <main>
         <p>
@@ -64,8 +70,15 @@
               is="iron-input"
               id="name"
               bind-value="{{_account.name}}"
-              disabled="[[_saving]]"
-              on-keydown="_handleNameKeydown">
+              disabled="[[_saving]]">
+        </section>
+        <section>
+          <div class="title">Username</div>
+          <input
+              is="iron-input"
+              id="username"
+              bind-value="{{_account.username}}"
+              disabled="[[_saving]]">
         </section>
         <section>
           <div class="title">Preferred Email</div>
@@ -78,19 +91,26 @@
             </template>
           </select>
         </section>
+        <hr>
+        <p>
+          More configuration options for Gerrit may be found in the
+          <a on-tap="close" href$="[[_computeSettingsUrl(_account)]]">settings</a>.
+        </p>
       </main>
       <footer>
         <gr-button
-            id="saveButton"
-            primary
-            disabled="[[_saving]]"
-            on-tap="_handleSave">Save</gr-button>
-        <gr-button
             id="closeButton"
+            link
             disabled="[[_saving]]"
             on-tap="_handleClose">Close</gr-button>
+        <gr-button
+            id="saveButton"
+            primary
+            link
+            disabled="[[_computeSaveDisabled(_account.name, _account.username, _account.email, _saving)]]"
+            on-tap="_handleSave">Save</gr-button>
       </footer>
-    </main>
+    </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-registration-dialog.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
index 71d80eb..406d16c 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
@@ -31,8 +31,18 @@
 
     properties: {
       /** @type {?} */
-      _account: Object,
-      _saving: Boolean,
+      _account: {
+        type: Object,
+        value: () => {
+          // Prepopulate possibly undefined fields with values to trigger
+          // computed bindings.
+          return {email: null, name: null, username: null};
+        },
+      },
+      _saving: {
+        type: Boolean,
+        value: false,
+      },
     },
 
     hostAttributes: {
@@ -41,22 +51,19 @@
 
     attached() {
       this.$.restAPI.getAccount().then(account => {
-        this._account = account;
+        // Using Object.assign here allows preservation of the default values
+        // supplied in the value generating function of this._account, unless
+        // they are overridden by properties in the account from the response.
+        this._account = Object.assign({}, this._account, account);
       });
     },
 
-    _handleNameKeydown(e) {
-      if (e.keyCode === 13) { // Enter
-        e.stopPropagation();
-        this._save();
-      }
-    },
-
     _save() {
       this._saving = true;
       const promises = [
         this.$.restAPI.setAccountName(this.$.name.value),
-        this.$.restAPI.setPreferredAccountEmail(this.$.email.value),
+        this.$.restAPI.setAccountUsername(this.$.username.value),
+        this.$.restAPI.setPreferredAccountEmail(this.$.email.value || ''),
       ];
       return Promise.all(promises).then(() => {
         this._saving = false;
@@ -66,15 +73,25 @@
 
     _handleSave(e) {
       e.preventDefault();
-      this._save().then(() => {
-        this.fire('close');
-      });
+      this._save().then(this.close.bind(this));
     },
 
     _handleClose(e) {
       e.preventDefault();
+      this.close();
+    },
+
+    close() {
       this._saving = true; // disable buttons indefinitely
       this.fire('close');
     },
+
+    _computeSaveDisabled(name, username, email, saving) {
+      return !name || !username || !email || saving;
+    },
+
+    _computeSettingsUrl() {
+      return Gerrit.Nav.getUrlForSettings();
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
index 858c3ae..15b4fa2 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
@@ -41,13 +41,16 @@
   suite('gr-registration-dialog tests', () => {
     let element;
     let account;
+    let sandbox;
     let _listeners;
 
     setup(done => {
+      sandbox = sinon.sandbox.create();
       _listeners = {};
 
       account = {
         name: 'name',
+        username: 'username',
         email: 'email',
         secondary_emails: [
           'email2',
@@ -65,6 +68,10 @@
           account.name = name;
           return Promise.resolve();
         },
+        setAccountUsername(username) {
+          account.username = username;
+          return Promise.resolve();
+        },
         setPreferredAccountEmail(email) {
           account.email = email;
           return Promise.resolve();
@@ -75,6 +82,7 @@
     });
 
     teardown(() => {
+      sandbox.restore();
       for (const eventType in _listeners) {
         if (_listeners.hasOwnProperty(eventType)) {
           element.removeEventListener(eventType, _listeners[eventType]);
@@ -119,32 +127,26 @@
       }).then(done);
     });
 
-    test('saves name and preferred email', done => {
+    test('saves account details', done => {
       flush(() => {
         element.$.name.value = 'new name';
+        element.$.username.value = 'new username';
         element.$.email.value = 'email3';
 
         // Nothing should be committed yet.
         assert.equal(account.name, 'name');
+        assert.equal(account.username, 'username');
         assert.equal(account.email, 'email');
 
         // Save and verify new values are committed.
         save().then(() => {
           assert.equal(account.name, 'new name');
+          assert.equal(account.username, 'new username');
           assert.equal(account.email, 'email3');
         }).then(done);
       });
     });
 
-    test('pressing enter saves name', done => {
-      element.$.name.value = 'entered name';
-      save(() => {
-        MockInteractions.pressAndReleaseKeyOn(element.$.name, 13); // 'enter'
-      }).then(() => {
-        assert.equal(account.name, 'entered name');
-      }).then(done);
-    });
-
     test('email select properly populated', done => {
       element._account = {email: 'foo', secondary_emails: ['bar', 'baz']};
       flush(() => {
@@ -152,5 +154,15 @@
         done();
       });
     });
+
+    test('save btn disabled', () => {
+      const compute = element._computeSaveDisabled;
+      assert.isTrue(compute('', '', '', false));
+      assert.isTrue(compute('', 'test', 'test', false));
+      assert.isTrue(compute('test', '', 'test', false));
+      assert.isTrue(compute('test', 'test', '', false));
+      assert.isTrue(compute('test', 'test', 'test', true));
+      assert.isFalse(compute('test', 'test', 'test', false));
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
index c34bc69..1744d28 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
@@ -24,7 +24,7 @@
         --gr-tooltip-arrow-size: .5em;
         --gr-tooltip-arrow-center-offset: 0;
 
-        background-color: #333;
+        background-color: var(--tooltip-background-color, #333);
         box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
         color: #fff;
         font-size: .75rem;
@@ -52,11 +52,11 @@
         width: 0;
       }
       .arrowPositionAbove {
-        border-top: var(--gr-tooltip-arrow-size) solid #333;
+        border-top: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color, #333);
         bottom: -var(--gr-tooltip-arrow-size);
       }
       .arrowPositionBelow {
-        border-bottom: var(--gr-tooltip-arrow-size) solid #333;
+        border-bottom: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color, #333);
         top: -var(--gr-tooltip-arrow-size);
       }
     </style>