Merge "Convert gr-tooltip to lit-element"
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
index 0585aec8..4315071 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
@@ -210,13 +210,9 @@
     const left = rect.left - parentRect.left + (rect.width - boxRect.width) / 2;
     const right = parentRect.width - left - boxRect.width;
     if (left < 0) {
-      tooltip.updateStyles({
-        '--gr-tooltip-arrow-center-offset': `${left}px`,
-      });
+      tooltip.arrowCenterOffset = `${left}px`;
     } else if (right < 0) {
-      tooltip.updateStyles({
-        '--gr-tooltip-arrow-center-offset': `${-0.5 * right}px`,
-      });
+      tooltip.arrowCenterOffset = `${-0.5 * right}px`;
     }
     tooltip.style.left = `${Math.max(0, left)}px`;
 
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.js b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.js
index 8d3bbb0..98ba237 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.js
@@ -25,8 +25,8 @@
 
   function makeTooltip(tooltipRect, parentRect) {
     return {
+      arrowCenterOffset: '0',
       getBoundingClientRect() { return tooltipRect; },
-      updateStyles: sinon.stub(),
       style: {left: 0, top: 0},
       parentElement: {
         getBoundingClientRect() { return parentRect; },
@@ -66,12 +66,12 @@
         {top: 0, left: 0, width: 1000});
 
     element._positionTooltip(tooltip);
-    assert.isFalse(tooltip.updateStyles.called);
+    assert.equal(tooltip.arrowCenterOffset, '0');
     assert.equal(tooltip.style.left, '175px');
     assert.equal(tooltip.style.top, '100px');
   });
 
-  test('left side position', () => {
+  test('left side position', async () => {
     sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
       return {top: 100, left: 10, width: 50};
     });
@@ -80,10 +80,8 @@
         {top: 0, left: 0, width: 1000});
 
     element._positionTooltip(tooltip);
-    assert.isTrue(tooltip.updateStyles.called);
-    const offset = tooltip.updateStyles
-        .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
-    assert.isBelow(parseFloat(offset.replace(/px$/, '')), 0);
+    await element.updateComplete;
+    assert.isBelow(parseFloat(tooltip.arrowCenterOffset.replace(/px$/, '')), 0);
     assert.equal(tooltip.style.left, '0px');
     assert.equal(tooltip.style.top, '100px');
   });
@@ -97,10 +95,7 @@
         {top: 0, left: 0, width: 1000});
 
     element._positionTooltip(tooltip);
-    assert.isTrue(tooltip.updateStyles.called);
-    const offset = tooltip.updateStyles
-        .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
-    assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
+    assert.isAbove(parseFloat(tooltip.arrowCenterOffset.replace(/px$/, '')), 0);
     assert.equal(tooltip.style.left, '915px');
     assert.equal(tooltip.style.top, '100px');
   });
@@ -115,19 +110,16 @@
 
     element.positionBelow = true;
     element._positionTooltip(tooltip);
-    assert.isTrue(tooltip.updateStyles.called);
-    const offset = tooltip.updateStyles
-        .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
-    assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
+    assert.isAbove(parseFloat(tooltip.arrowCenterOffset.replace(/px$/, '')), 0);
     assert.equal(tooltip.style.left, '915px');
     assert.equal(tooltip.style.top, '157.2px');
   });
 
   test('hides tooltip when detached', async () => {
-    sinon.stub(element, '_handleHideTooltip');
+    const handleHideTooltipStub = sinon.stub(element, '_handleHideTooltip');
     element.remove();
     await element.updateComplete;
-    assert.isTrue(element._handleHideTooltip.called);
+    assert.isTrue(handleHideTooltipStub.called);
   });
 
   test('sets up listeners when has-tooltip is changed', async () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.ts b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.ts
index cab05b4..0e41891 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.ts
@@ -14,14 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-tooltip_html';
-import {customElement, property, observe} from '@polymer/decorators';
 
-export interface GrTooltip {
-  $: {};
-}
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {styleMap} from 'lit/directives/style-map';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -30,22 +27,78 @@
 }
 
 @customElement('gr-tooltip')
-export class GrTooltip extends PolymerElement {
-  static get template() {
-    return htmlTemplate;
-  }
-
+export class GrTooltip extends LitElement {
   @property({type: String})
   text = '';
 
   @property({type: String})
   maxWidth = '';
 
-  @property({type: Boolean, reflectToAttribute: true})
+  @property({type: String})
+  arrowCenterOffset = '0';
+
+  @property({type: Boolean, reflect: true, attribute: 'position-below'})
   positionBelow = false;
 
-  @observe('maxWidth')
-  _updateWidth(maxWidth: string) {
-    this.updateStyles({'--tooltip-max-width': maxWidth});
+  static override get styles() {
+    return [
+      sharedStyles,
+      css`
+        :host {
+          --gr-tooltip-arrow-size: 0.5em;
+
+          background-color: var(--tooltip-background-color);
+          box-shadow: var(--elevation-level-2);
+          color: var(--tooltip-text-color);
+          font-size: var(--font-size-small);
+          position: absolute;
+          z-index: 1000;
+        }
+        :host .tooltip {
+          padding: var(--spacing-m) var(--spacing-l);
+        }
+        :host .arrowPositionBelow,
+        :host([position-below]) .arrowPositionAbove {
+          display: none;
+        }
+        :host([position-below]) .arrowPositionBelow {
+          display: initial;
+        }
+        .arrow {
+          border-left: var(--gr-tooltip-arrow-size) solid transparent;
+          border-right: var(--gr-tooltip-arrow-size) solid transparent;
+          height: 0;
+          position: absolute;
+          left: calc(50% - var(--gr-tooltip-arrow-size));
+          width: 0;
+        }
+        .arrowPositionAbove {
+          border-top: var(--gr-tooltip-arrow-size) solid
+            var(--tooltip-background-color);
+          bottom: calc(-1 * var(--gr-tooltip-arrow-size));
+        }
+        .arrowPositionBelow {
+          border-bottom: var(--gr-tooltip-arrow-size) solid
+            var(--tooltip-background-color);
+          top: calc(-1 * var(--gr-tooltip-arrow-size));
+        }
+      `,
+    ];
+  }
+
+  override render() {
+    this.style.maxWidth = this.maxWidth;
+
+    return html` <div class="tooltip">
+      <i
+        class="arrowPositionBelow arrow"
+        style="${styleMap({marginLeft: this.arrowCenterOffset})}"
+      ></i>
+      ${this.text}
+      <i
+        class="arrowPositionAbove arrow"
+        style="${styleMap({marginLeft: this.arrowCenterOffset})}"
+      ></i>
+    </div>`;
   }
 }
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_html.ts b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_html.ts
deleted file mode 100644
index d59a6c3..0000000
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_html.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
-  <style include="shared-styles">
-    :host {
-      --gr-tooltip-arrow-size: 0.5em;
-      --gr-tooltip-arrow-center-offset: 0;
-
-      background-color: var(--tooltip-background-color);
-      box-shadow: var(--elevation-level-2);
-      color: var(--tooltip-text-color);
-      font-size: var(--font-size-small);
-      position: absolute;
-      z-index: 1000;
-      max-width: var(--tooltip-max-width);
-    }
-    :host .tooltip {
-      padding: var(--spacing-m) var(--spacing-l);
-    }
-    :host .arrowPositionBelow,
-    :host([position-below]) .arrowPositionAbove {
-      display: none;
-    }
-    :host([position-below]) .arrowPositionBelow {
-      display: initial;
-    }
-    .arrow {
-      border-left: var(--gr-tooltip-arrow-size) solid transparent;
-      border-right: var(--gr-tooltip-arrow-size) solid transparent;
-      height: 0;
-      position: absolute;
-      left: calc(50% - var(--gr-tooltip-arrow-size));
-      margin-left: var(--gr-tooltip-arrow-center-offset);
-      width: 0;
-    }
-    .arrowPositionAbove {
-      border-top: var(--gr-tooltip-arrow-size) solid
-        var(--tooltip-background-color);
-      bottom: calc(-1 * var(--gr-tooltip-arrow-size));
-    }
-    .arrowPositionBelow {
-      border-bottom: var(--gr-tooltip-arrow-size) solid
-        var(--tooltip-background-color);
-      top: calc(-1 * var(--gr-tooltip-arrow-size));
-    }
-  </style>
-  <div class="tooltip">
-    <i class="arrowPositionBelow arrow"></i>
-    [[text]]
-    <i class="arrowPositionAbove arrow"></i>
-  </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.ts b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.ts
index 8b44047..b693a9e 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.ts
@@ -17,49 +17,45 @@
 
 import '../../../test/common-test-setup-karma';
 import './gr-tooltip';
-import {html} from '@polymer/polymer/lib/utils/html-tag';
 import {GrTooltip} from './gr-tooltip';
+import {queryAndAssert} from '../../../test/test-utils';
 
-const basicFixture = fixtureFromTemplate(html` <gr-tooltip> </gr-tooltip> `);
+const basicFixture = fixtureFromElement('gr-tooltip');
 
 suite('gr-tooltip tests', () => {
   let element: GrTooltip;
-  setup(() => {
+
+  setup(async () => {
     element = basicFixture.instantiate() as GrTooltip;
+    await element.updateComplete;
   });
 
-  test('max-width is respected if set', () => {
+  test('max-width is respected if set', async () => {
     element.text =
       'Lorem ipsum dolor sit amet, consectetur adipiscing elit' +
       ', sed do eiusmod tempor incididunt ut labore et dolore magna aliqua';
     element.maxWidth = '50px';
+    await element.updateComplete;
     assert.equal(getComputedStyle(element).width, '50px');
   });
 
-  test('the correct arrow is displayed', () => {
+  test('the correct arrow is displayed', async () => {
     assert.equal(
-      getComputedStyle(
-        element.shadowRoot!.querySelector('.arrowPositionBelow')!
-      ).display,
+      getComputedStyle(queryAndAssert(element, '.arrowPositionBelow')!).display,
       'none'
     );
     assert.notEqual(
-      getComputedStyle(
-        element.shadowRoot!.querySelector('.arrowPositionAbove')!
-      ).display,
+      getComputedStyle(queryAndAssert(element, '.arrowPositionAbove')!).display,
       'none'
     );
     element.positionBelow = true;
+    await element.updateComplete;
     assert.notEqual(
-      getComputedStyle(
-        element.shadowRoot!.querySelector('.arrowPositionBelow')!
-      ).display,
+      getComputedStyle(queryAndAssert(element, '.arrowPositionBelow')!).display,
       'none'
     );
     assert.equal(
-      getComputedStyle(
-        element.shadowRoot!.querySelector('.arrowPositionAbove')!
-      ).display,
+      getComputedStyle(queryAndAssert(element, '.arrowPositionAbove')!).display,
       'none'
     );
   });