Migrate gr-download-commands to lit

Change-Id: I27a6d786bdcd2d52272a34a28c72533048838571
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
index 5fc7bc8..a3f7374 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
@@ -246,7 +246,10 @@
             )}
             .schemes=${this.schemes}
             .selectedScheme=${this.selectedScheme}
-            @selected-scheme-changed=${this.handleSelectedSchemeValueChanged}
+            @selected-scheme-changed=${(e: BindValueChangeEvent) => {
+              if (this.loading) return;
+              this.selectedScheme = e.detail.value;
+            }}
           ></gr-download-commands>
         </fieldset>
       </div>
@@ -1121,12 +1124,7 @@
     }
   }
 
-  private handleSelectedSchemeValueChanged(e: CustomEvent) {
-    if (this.loading) return;
-    this.selectedScheme = e.detail.value;
-  }
-
-  private handleDescriptionTextChanged(e: CustomEvent) {
+  private handleDescriptionTextChanged(e: BindValueChangeEvent) {
     if (!this.repoConfig || this.loading) return;
     this.repoConfig = {
       ...this.repoConfig,
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
index cc7e54ac..a72749b 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -28,8 +28,8 @@
 import {LitElement, PropertyValues, html, css} from 'lit';
 import {customElement, property, state, query} from 'lit/decorators';
 import {assertIsDefined} from '../../../utils/common-util';
-import {BindValueChangeEvent} from '../../../types/events';
 import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
+import {BindValueChangeEvent} from '../../../types/events';
 
 @customElement('gr-download-dialog')
 export class GrDownloadDialog extends LitElement {
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
index 6eb19da..64f97ca 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
@@ -18,53 +18,44 @@
 import '@polymer/paper-tabs/paper-tab';
 import '@polymer/paper-tabs/paper-tabs';
 import '../gr-shell-command/gr-shell-command';
-import '../../../styles/gr-paper-styles';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-download-commands_html';
-import {customElement, property} from '@polymer/decorators';
-import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
 import {getAppContext} from '../../../services/app-context';
 import {queryAndAssert} from '../../../utils/common-util';
 import {GrShellCommand} from '../gr-shell-command/gr-shell-command';
+import {paperStyles} from '../../../styles/gr-paper-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, html, css} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+import {fire} from '../../../utils/event-util';
+import {BindValueChangeEvent} from '../../../types/events';
 
 declare global {
   interface HTMLElementEventMap {
     'selected-changed': CustomEvent<{value: number}>;
+    'selected-scheme-changed': BindValueChangeEvent;
   }
   interface HTMLElementTagNameMap {
     'gr-download-commands': GrDownloadCommands;
   }
 }
 
-export interface GrDownloadCommands {
-  $: {
-    downloadTabs: PaperTabsElement;
-  };
-}
-
 export interface Command {
   title: string;
   command: string;
 }
 
 @customElement('gr-download-commands')
-export class GrDownloadCommands extends PolymerElement {
-  static get template() {
-    return htmlTemplate;
-  }
-
+export class GrDownloadCommands extends LitElement {
   // TODO(TS): maybe default to [] as only used in dom-repeat
   @property({type: Array})
   commands?: Command[];
 
-  @property({type: Boolean})
-  _loggedIn = false;
+  // private but used in test
+  @state() loggedIn = false;
 
   @property({type: Array})
   schemes: string[] = [];
 
-  @property({type: String, notify: true})
+  @property({type: String})
   selectedScheme?: string;
 
   @property({type: Boolean})
@@ -79,14 +70,15 @@
 
   override connectedCallback() {
     super.connectedCallback();
-    this._getLoggedIn().then(loggedIn => {
-      this._loggedIn = loggedIn;
+    this.restApiService.getLoggedIn().then(loggedIn => {
+      this.loggedIn = loggedIn;
     });
     this.subscriptions.push(
       this.userModel.preferences$.subscribe(prefs => {
         if (prefs?.download_scheme) {
           // Note (issue 5180): normalize the download scheme with lower-case.
           this.selectedScheme = prefs.download_scheme.toLowerCase();
+          fire(this, 'selected-scheme-changed', {value: this.selectedScheme});
         }
       })
     );
@@ -100,42 +92,121 @@
     super.disconnectedCallback();
   }
 
+  static override get styles() {
+    return [
+      paperStyles,
+      sharedStyles,
+      css`
+        paper-tabs {
+          height: 3rem;
+          margin-bottom: var(--spacing-m);
+          --paper-tabs-selection-bar-color: var(--link-color);
+        }
+        paper-tab {
+          max-width: 15rem;
+          text-transform: uppercase;
+          --paper-tab-ink: var(--link-color);
+        }
+        label,
+        input {
+          display: block;
+        }
+        label {
+          font-weight: var(--font-weight-bold);
+        }
+        .schemes {
+          display: flex;
+          justify-content: space-between;
+        }
+        .commands {
+          display: flex;
+          flex-direction: column;
+        }
+        gr-shell-command {
+          margin-bottom: var(--spacing-m);
+        }
+        .hidden {
+          display: none;
+        }
+      `,
+    ];
+  }
+
+  override render() {
+    return html`
+      <div class="schemes">${this.renderDownloadTabs()}</div>
+      ${this.renderCommands()}
+    `;
+  }
+
+  private renderDownloadTabs() {
+    if (this.schemes.length <= 1) return;
+
+    const selectedIndex =
+      this.schemes.findIndex(scheme => scheme === this.selectedScheme) || 0;
+    return html`
+      <paper-tabs
+        id="downloadTabs"
+        .selected=${selectedIndex}
+        @selected-changed=${this.handleTabChange}
+      >
+        ${this.schemes.map(scheme => this.renderPaperTab(scheme))}
+      </paper-tabs>
+    `;
+  }
+
+  private renderPaperTab(scheme: string) {
+    return html` <paper-tab data-scheme=${scheme}>${scheme}</paper-tab> `;
+  }
+
+  private renderCommands() {
+    if (!this.schemes.length) return;
+
+    return html`
+      <div class="commands">
+        ${this.commands?.map((command, index) =>
+          this.renderShellCommand(command, index)
+        )}
+      </div>
+    `;
+  }
+
+  private renderShellCommand(command: Command, index: number) {
+    return html`
+      <gr-shell-command
+        class="${this.computeClass(command.title)}"
+        .label=${command.title}
+        .command=${command.command}
+        .tooltip=${this.computeTooltip(index)}
+      ></gr-shell-command>
+    `;
+  }
+
   focusOnCopy() {
     queryAndAssert<GrShellCommand>(this, 'gr-shell-command').focusOnCopy();
   }
 
-  _getLoggedIn() {
-    return this.restApiService.getLoggedIn();
-  }
-
-  _handleTabChange(e: CustomEvent<{value: number}>) {
+  private handleTabChange = (e: CustomEvent<{value: number}>) => {
     const scheme = this.schemes[e.detail.value];
     if (scheme && scheme !== this.selectedScheme) {
-      this.set('selectedScheme', scheme);
-      if (this._loggedIn) {
+      this.selectedScheme = scheme;
+      fire(this, 'selected-scheme-changed', {value: scheme});
+      if (this.loggedIn) {
         this.userModel.updatePreferences({
           download_scheme: this.selectedScheme,
         });
       }
     }
-  }
+  };
 
-  _computeSelected(schemes: string[], selectedScheme?: string) {
-    return `${schemes.findIndex(scheme => scheme === selectedScheme) || 0}`;
-  }
-
-  _computeShowTabs(schemes: string[]) {
-    return schemes.length > 1 ? '' : 'hidden';
-  }
-
-  _computeTooltip(showKeyboardShortcutTooltips: boolean, index: number) {
-    return index <= 4 && showKeyboardShortcutTooltips
+  private computeTooltip(index: number) {
+    return index <= 4 && this.showKeyboardShortcutTooltips
       ? `Keyboard shortcut: ${index + 1}`
       : '';
   }
 
   // TODO: maybe unify with strToClassName from dom-util
-  _computeClass(title: string) {
+  private computeClass(title: string) {
     // Only retain [a-z] chars, so "Cherry Pick" becomes "cherrypick".
     return '_label_' + title.replace(/[^a-z]+/gi, '').toLowerCase();
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts
deleted file mode 100644
index f9c08ba..0000000
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts
+++ /dev/null
@@ -1,78 +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="gr-paper-styles">
-    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
-  </style>
-  <style include="shared-styles">
-    paper-tabs {
-      height: 3rem;
-      margin-bottom: var(--spacing-m);
-      --paper-tabs-selection-bar-color: var(--link-color);
-    }
-    paper-tab {
-      max-width: 15rem;
-      text-transform: uppercase;
-      --paper-tab-ink: var(--link-color);
-    }
-    label,
-    input {
-      display: block;
-    }
-    label {
-      font-weight: var(--font-weight-bold);
-    }
-    .schemes {
-      display: flex;
-      justify-content: space-between;
-    }
-    .commands {
-      display: flex;
-      flex-direction: column;
-    }
-    gr-shell-command {
-      margin-bottom: var(--spacing-m);
-    }
-    .hidden {
-      display: none;
-    }
-  </style>
-  <div class="schemes">
-    <paper-tabs
-      id="downloadTabs"
-      class$="[[_computeShowTabs(schemes)]]"
-      selected="[[_computeSelected(schemes, selectedScheme)]]"
-      on-selected-changed="_handleTabChange"
-    >
-      <template is="dom-repeat" items="[[schemes]]" as="scheme">
-        <paper-tab data-scheme$="[[scheme]]">[[scheme]]</paper-tab>
-      </template>
-    </paper-tabs>
-  </div>
-  <div class="commands" hidden$="[[!schemes.length]]" hidden="">
-    <template is="dom-repeat" items="[[commands]]" as="command" indexAs="index">
-      <gr-shell-command
-        class$="[[_computeClass(command.title)]]"
-        label="[[command.title]]"
-        command="[[command.command]]"
-        tooltip="[[_computeTooltip(showKeyboardShortcutTooltips, index)]]"
-      ></gr-shell-command>
-    </template>
-  </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
index bd0ca70..eb9490f 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
@@ -18,11 +18,12 @@
 import '../../../test/common-test-setup-karma';
 import './gr-download-commands';
 import {GrDownloadCommands} from './gr-download-commands';
-import {isHidden, queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {query, queryAndAssert, stubRestApi} from '../../../test/test-utils';
 import {createPreferences} from '../../../test/test-data-generators';
 import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
 import {GrShellCommand} from '../gr-shell-command/gr-shell-command';
 import {createDefaultPreferences} from '../../../constants/constants';
+import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
 
 const basicFixture = fixtureFromElement('gr-download-commands');
 
@@ -63,7 +64,7 @@
       element.schemes = SCHEMES;
       element.commands = COMMANDS;
       element.selectedScheme = SELECTED_SCHEME;
-      await flush();
+      await element.updateComplete;
     });
 
     test('focusOnCopy', () => {
@@ -75,30 +76,37 @@
       assert.isTrue(focusStub.called);
     });
 
-    test('element visibility', () => {
-      assert.isFalse(isHidden(queryAndAssert(element, 'paper-tabs')));
-      assert.isFalse(isHidden(queryAndAssert(element, '.commands')));
+    test('element visibility', async () => {
+      assert.isTrue(Boolean(query(element, 'paper-tabs')));
+      assert.isTrue(Boolean(query(element, '.commands')));
 
       element.schemes = [];
-      assert.isTrue(isHidden(queryAndAssert(element, 'paper-tabs')));
-      assert.isTrue(isHidden(queryAndAssert(element, '.commands')));
+      await element.updateComplete;
+      assert.isFalse(Boolean(query(element, 'paper-tabs')));
+      assert.isFalse(Boolean(query(element, '.commands')));
     });
 
-    test('tab selection', () => {
-      assert.equal(element.$.downloadTabs.selected, '0');
+    test('tab selection', async () => {
+      assert.equal(
+        queryAndAssert<PaperTabsElement>(element, '#downloadTabs').selected,
+        '0'
+      );
       MockInteractions.tap(queryAndAssert(element, '[data-scheme="ssh"]'));
-      flush();
+      await element.updateComplete;
       assert.equal(element.selectedScheme, 'ssh');
-      assert.equal(element.$.downloadTabs.selected, '2');
+      assert.equal(
+        queryAndAssert<PaperTabsElement>(element, '#downloadTabs').selected,
+        '2'
+      );
     });
 
-    test('saves scheme to preferences', () => {
-      element._loggedIn = true;
+    test('saves scheme to preferences', async () => {
+      element.loggedIn = true;
       const savePrefsStub = stubRestApi('savePreferences').returns(
         Promise.resolve(createDefaultPreferences())
       );
 
-      flush();
+      await element.updateComplete;
 
       const repoTab = queryAndAssert(element, 'paper-tab[data-scheme="repo"]');
 
@@ -114,7 +122,7 @@
   suite('authenticated', () => {
     test('loads scheme from preferences', async () => {
       const element = basicFixture.instantiate();
-      await flush();
+      await element.updateComplete;
       element.userModel.setPreferences({
         ...createPreferences(),
         download_scheme: 'repo',
@@ -124,7 +132,7 @@
 
     test('normalize scheme from preferences', async () => {
       const element = basicFixture.instantiate();
-      await flush();
+      await element.updateComplete;
       element.userModel.setPreferences({
         ...createPreferences(),
         download_scheme: 'REPO',