Migrate gr-limited-text to Lit

 - Migrate gr-limited-text to be a LitElement
 - Remove usage of TooltipMixin in gr-limited-text

Google-Bug-Id: b/199305412
Change-Id: If02d10bf10dbf12fb833ba2df271d594c2ec2123
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
index f5a0c4d..7008db2 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
@@ -14,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-limited-text_html';
-import {TooltipMixin} from '../../../mixins/gr-tooltip-mixin/gr-tooltip-mixin';
-import {customElement, observe, property} from '@polymer/decorators';
+import {customElement, property} from 'lit/decorators';
+import {html, LitElement} from 'lit';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -25,9 +23,6 @@
   }
 }
 
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = TooltipMixin(PolymerElement);
-
 /**
  * The gr-limited-text element is for displaying text with a maximum length
  * (in number of characters) to display. If the length of the text exceeds the
@@ -35,57 +30,56 @@
  * and a tooltip containing the full text is enabled.
  */
 @customElement('gr-limited-text')
-export class GrLimitedText extends base {
-  static get template() {
-    return htmlTemplate;
-  }
-
+export class GrLimitedText extends LitElement {
   /** The un-truncated text to display. */
   @property({type: String})
-  text?: string;
+  text = '';
 
   /** The maximum length for the text to display before truncating. */
   @property({type: Number})
-  limit?: number;
+  limit = 0;
 
   @property({type: String})
   tooltip?: string;
 
-  /** Boolean property used by TooltipMixin. */
-  @property({type: Boolean})
-  override hasTooltip = false;
+  static override get styles() {
+    return [];
+  }
 
-  /** Boolean property used by TooltipMixin. */
-  @property({type: Boolean})
-  disableTooltip = false;
-
-  /**
-   * The text or limit have changed. Recompute whether a tooltip needs to be
-   * enabled.
-   */
-  @observe('text', 'tooltip', 'limit')
-  _updateTitle(text?: string, tooltip?: string, limit?: number) {
-    text = text ?? '';
-    tooltip = tooltip ?? '';
-    limit = limit ?? 0;
-
-    this.hasTooltip = !!tooltip || (!!limit && text.length > limit);
-    if (this.hasTooltip && !this.disableTooltip) {
-      // Combine the text and title if over-length
-      if (limit && text.length > limit) {
-        this.title = `${text}${tooltip ? ` (${tooltip})` : ''}`;
-      } else {
-        this.title = tooltip;
-      }
+  override render() {
+    if (this.tooltip || this.tooLong()) {
+      return html` <gr-tooltip-content
+        has-tooltip
+        .title=${this.renderTooltip()}
+      >
+        ${this.renderText()}
+      </gr-tooltip-content>`;
     } else {
-      this.title = '';
+      return this.renderText();
     }
   }
 
-  _computeDisplayText(text?: string, limit?: number) {
-    if (!!limit && !!text && text.length > limit) {
-      return text.substr(0, limit - 1) + '…';
+  // Should be private but used in tests.
+  renderText() {
+    if (this.tooLong()) {
+      return this.text.substr(0, this.limit - 1) + '…';
     }
-    return text;
+    return this.text;
+  }
+
+  private renderTooltip() {
+    if (this.tooLong()) {
+      return `${this.text}${this.tooltip ? ` (${this.tooltip})` : ''}`;
+    } else if (this.tooltip) {
+      return this.tooltip;
+    } else {
+      return '';
+    }
+  }
+
+  private tooLong() {
+    if (!this.limit) return false;
+    if (!this.text) return false;
+    return this.text.length > this.limit;
   }
 }
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_html.ts b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_html.ts
deleted file mode 100644
index b942d07..0000000
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_html.ts
+++ /dev/null
@@ -1,19 +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` [[_computeDisplayText(text, limit)]] `;
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.js b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.js
index 3b99d6d..e3e72d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.js
@@ -23,77 +23,65 @@
 suite('gr-limited-text tests', () => {
   let element;
 
-  setup(() => {
+  setup(async () => {
     element = basicFixture.instantiate();
+    await element.updateComplete;
   });
 
-  test('tooltip without title input', () => {
-    const updateSpy = sinon.spy(element, '_updateTitle');
+  test('tooltip without title input', async () => {
     element.text = 'abc 123';
-    flush();
-    assert.isTrue(updateSpy.calledOnce);
-    assert.isNotOk(element.getAttribute('title'));
-    assert.isFalse(element.hasTooltip);
+    await element.updateComplete;
+    assert.isNotOk(element.shadowRoot.querySelector('gr-tooltip-content'));
 
     element.limit = 10;
-    flush();
-    assert.isTrue(updateSpy.calledTwice);
-    assert.isNotOk(element.getAttribute('title'));
-    assert.isFalse(element.hasTooltip);
+    await element.updateComplete;
+    assert.isNotOk(element.shadowRoot.querySelector('gr-tooltip-content'));
 
     element.limit = 3;
-    flush();
-    assert.equal(updateSpy.callCount, 3);
-    assert.equal(element.getAttribute('title'), 'abc 123');
-    assert.equal(element.title, 'abc 123');
-    assert.isTrue(element.hasTooltip);
+    await element.updateComplete;
+    assert.isOk(element.shadowRoot.querySelector('gr-tooltip-content'));
+    assert.equal(
+        element.shadowRoot.querySelector('gr-tooltip-content').title,
+        'abc 123');
 
     element.limit = 100;
-    flush();
-    assert.equal(updateSpy.callCount, 4);
-    assert.isFalse(element.hasTooltip);
+    await element.updateComplete;
+    assert.isNotOk(element.shadowRoot.querySelector('gr-tooltip-content'));
 
     element.limit = null;
-    flush();
-    assert.equal(updateSpy.callCount, 5);
-    assert.isNotOk(element.getAttribute('title'));
-    assert.isFalse(element.hasTooltip);
+    await element.updateComplete;
+    assert.isNotOk(element.shadowRoot.querySelector('gr-tooltip-content'));
   });
 
-  test('with tooltip input', () => {
-    const updateSpy = sinon.spy(element, '_updateTitle');
+  test('with tooltip input', async () => {
     element.tooltip = 'abc 123';
-    flush();
-    assert.isTrue(updateSpy.calledOnce);
-    assert.isTrue(element.hasTooltip);
-    assert.equal(element.getAttribute('title'), 'abc 123');
-    assert.equal(element.title, 'abc 123');
+    await element.updateComplete;
+    let tooltipContent = element.shadowRoot.querySelector('gr-tooltip-content');
+    assert.isOk(tooltipContent);
+    assert.equal(tooltipContent.title, 'abc 123');
 
     element.text = 'abc';
-    flush();
-    assert.equal(element.getAttribute('title'), 'abc 123');
-    assert.isTrue(element.hasTooltip);
+    await element.updateComplete;
+    tooltipContent = element.shadowRoot.querySelector('gr-tooltip-content');
+    assert.isOk(tooltipContent);
+    assert.equal(tooltipContent.title, 'abc 123');
 
     element.text = 'abcdef';
     element.limit = 3;
-    flush();
-    assert.equal(element.getAttribute('title'), 'abcdef (abc 123)');
-    assert.isTrue(element.hasTooltip);
+    await element.updateComplete;
+    tooltipContent = element.shadowRoot.querySelector('gr-tooltip-content');
+    assert.isOk(tooltipContent);
+    assert.equal(tooltipContent.title, 'abcdef (abc 123)');
   });
 
   test('_computeDisplayText', () => {
-    assert.equal(element._computeDisplayText('foo bar', 100), 'foo bar');
-    assert.equal(element._computeDisplayText('foo bar', 4), 'foo…');
-    assert.equal(element._computeDisplayText('foo bar', null), 'foo bar');
-  });
-
-  test('when disable tooltip', () => {
-    sinon.spy(element, '_updateTitle');
-    element.text = 'abcdefghijklmn';
-    element.disableTooltip = true;
-    element.limit = 10;
-    flush();
-    assert.equal(element.getAttribute('title'), '');
+    element.text = 'foo bar';
+    element.limit = 100;
+    assert.equal(element.renderText(), 'foo bar');
+    element.limit = 4;
+    assert.equal(element.renderText(), 'foo…');
+    element.limit = 0;
+    assert.equal(element.renderText(), 'foo bar');
   });
 });