Merge "Migrate plugin endpoint components to lit" into stable-3.6
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
index 8a01ef7..64548ac 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
@@ -1,39 +1,24 @@
 /**
  * @license
- * Copyright (C) 2017 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.
+ * Copyright 2017 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
  */
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-endpoint-decorator_html';
+import {html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators';
 import {
   getPluginEndpoints,
   ModuleInfo,
 } from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
 import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
-import {customElement, property} from '@polymer/decorators';
 import {PluginApi} from '../../../api/plugin';
 import {HookApi, PluginElement} from '../../../api/hook';
 import {getAppContext} from '../../../services/app-context';
+import {assertIsDefined} from '../../../utils/common-util';
 
 const INIT_PROPERTIES_TIMEOUT_MS = 10000;
 
 @customElement('gr-endpoint-decorator')
-export class GrEndpointDecorator extends PolymerElement {
-  static get template() {
-    return htmlTemplate;
-  }
-
+export class GrEndpointDecorator extends LitElement {
   /**
    * If set, then this endpoint only invokes callbacks registered by the target
    * plugin. For example this is used for the `check-result-expanded` endpoint.
@@ -43,39 +28,51 @@
   @property({type: String})
   targetPlugin?: string;
 
+  /** Required. */
   @property({type: String})
-  name!: string;
+  name?: string;
 
-  @property({type: Object})
-  _domHooks = new Map<PluginElement, HookApi<PluginElement>>();
+  private readonly domHooks = new Map<PluginElement, HookApi<PluginElement>>();
 
-  @property({type: Object})
-  _initializedPlugins = new Map<string, boolean>();
-
-  /**
-   * This is the callback that the plugin endpoint manager should be calling
-   * when a new element is registered for this endpoint. It points to
-   * _initModule().
-   */
-  _endpointCallBack: (info: ModuleInfo) => void = () => {};
+  private readonly initializedPlugins = new Map<string, boolean>();
 
   private readonly reporting = getAppContext().reportingService;
 
+  override render() {
+    return html`<slot></slot>`;
+  }
+
+  override connectedCallback() {
+    super.connectedCallback();
+    assertIsDefined(this.name);
+    getPluginEndpoints().onNewEndpoint(this.name, this.initModule);
+    getPluginLoader()
+      .awaitPluginsLoaded()
+      .then(() => {
+        assertIsDefined(this.name);
+        const modules = getPluginEndpoints().getDetails(this.name);
+        for (const module of modules) {
+          this.initModule(module);
+        }
+      });
+  }
+
   override disconnectedCallback() {
-    for (const [el, domHook] of this._domHooks) {
+    for (const [el, domHook] of this.domHooks) {
       domHook.handleInstanceDetached(el);
     }
-    getPluginEndpoints().onDetachedEndpoint(this.name, this._endpointCallBack);
+    assertIsDefined(this.name);
+    getPluginEndpoints().onDetachedEndpoint(this.name, this.initModule);
     super.disconnectedCallback();
   }
 
-  _initDecoration(
+  private initDecoration(
     name: string,
     plugin: PluginApi,
     slot?: string
   ): Promise<HTMLElement> {
     const el = document.createElement(name) as PluginElement;
-    return this._initProperties(
+    return this.initProperties(
       el,
       plugin,
       // The direct children are slotted into <slot>, so this is identical to
@@ -88,13 +85,16 @@
       if (slot && slotEl?.parentNode) {
         slotEl.parentNode.insertBefore(el, slotEl.nextSibling);
       } else {
-        this._appendChild(el);
+        this.appendChild(el);
       }
       return el;
     });
   }
 
-  _initReplacement(name: string, plugin: PluginApi): Promise<HTMLElement> {
+  private initReplacement(
+    name: string,
+    plugin: PluginApi
+  ): Promise<HTMLElement> {
     // The direct children are slotted into <slot>, so they are identical to
     // this.shadowRoot.querySelector('slot').assignedElements().
     const directChildren = [...this.childNodes];
@@ -104,16 +104,16 @@
       .filter(node => node.nodeName !== 'SLOT')
       .forEach(node => node.remove());
     const el = document.createElement(name);
-    return this._initProperties(el, plugin).then((el: HTMLElement) =>
-      this._appendChild(el)
+    return this.initProperties(el, plugin).then((el: HTMLElement) =>
+      this.appendChild(el)
     );
   }
 
-  _getEndpointParams() {
+  private getEndpointParams() {
     return Array.from(this.querySelectorAll('gr-endpoint-param'));
   }
 
-  _initProperties(
+  private initProperties(
     el: PluginElement,
     plugin: PluginApi,
     content?: Element | null
@@ -128,7 +128,7 @@
     if (content) {
       el.content = content as HTMLElement;
     }
-    const expectProperties = this._getEndpointParams().map(paramEl => {
+    const expectProperties = this.getEndpointParams().map(paramEl => {
       const helper = plugin.attributeHelper(paramEl);
       // TODO: this should be replaced by accessing the property directly
       const paramName = paramEl.getAttribute('name');
@@ -170,53 +170,40 @@
       });
   }
 
-  _appendChild(el: HTMLElement): HTMLElement {
-    if (!this.root) throw Error('plugin endpoint decorator missing root');
-    return this.root.appendChild(el);
-  }
-
-  _initModule({moduleName, plugin, type, domHook, slot}: ModuleInfo) {
+  private readonly initModule = ({
+    moduleName,
+    plugin,
+    type,
+    domHook,
+    slot,
+  }: ModuleInfo) => {
     const name = plugin.getPluginName() + '.' + moduleName;
     if (this.targetPlugin) {
       if (this.targetPlugin !== plugin.getPluginName()) return;
     }
-    if (this._initializedPlugins.get(name)) {
+    if (this.initializedPlugins.get(name)) {
       return;
     }
     let initPromise;
     switch (type) {
       case 'decorate':
-        initPromise = this._initDecoration(moduleName, plugin, slot);
+        initPromise = this.initDecoration(moduleName, plugin, slot);
         break;
       case 'replace':
-        initPromise = this._initReplacement(moduleName, plugin);
+        initPromise = this.initReplacement(moduleName, plugin);
         break;
     }
     if (!initPromise) {
       throw Error(`unknown endpoint type ${type} used by plugin ${name}`);
     }
-    this._initializedPlugins.set(name, true);
+    this.initializedPlugins.set(name, true);
     initPromise.then(el => {
       if (domHook) {
         domHook.handleInstanceAttached(el);
-        this._domHooks.set(el, domHook);
+        this.domHooks.set(el, domHook);
       }
     });
-  }
-
-  override ready() {
-    super.ready();
-    if (!this.name) return;
-    this._endpointCallBack = (info: ModuleInfo) => this._initModule(info);
-    getPluginEndpoints().onNewEndpoint(this.name, this._endpointCallBack);
-    getPluginLoader()
-      .awaitPluginsLoaded()
-      .then(() =>
-        getPluginEndpoints()
-          .getDetails(this.name)
-          .forEach(this._initModule, this)
-      );
-  }
+  };
 }
 
 declare global {
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_html.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_html.ts
deleted file mode 100644
index 94196df..0000000
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_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` <slot></slot> `;
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.ts
index f5096ea..d7acc61 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.ts
@@ -1,60 +1,75 @@
 /**
  * @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.
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
  */
-
 import '../../../test/common-test-setup-karma';
 import './gr-endpoint-decorator';
 import '../gr-endpoint-param/gr-endpoint-param';
 import '../gr-endpoint-slot/gr-endpoint-slot';
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-import {resetPlugins} from '../../../test/test-utils';
+import {fixture, html} from '@open-wc/testing-helpers';
+import {
+  mockPromise,
+  queryAndAssert,
+  resetPlugins,
+} from '../../../test/test-utils';
 import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
 import {GrEndpointDecorator} from './gr-endpoint-decorator';
 import {PluginApi} from '../../../api/plugin';
 import {GrEndpointParam} from '../gr-endpoint-param/gr-endpoint-param';
 
-const basicFixture = fixtureFromTemplate(
-  html`<div>
-    <gr-endpoint-decorator name="first">
-      <gr-endpoint-param name="someparam" value="barbar"></gr-endpoint-param>
-      <p>
-        <span>test slot</span>
-        <gr-endpoint-slot name="test"></gr-endpoint-slot>
-      </p>
-    </gr-endpoint-decorator>
-    <gr-endpoint-decorator name="second">
-      <gr-endpoint-param name="someparam" value="foofoo"></gr-endpoint-param>
-    </gr-endpoint-decorator>
-    <gr-endpoint-decorator name="banana">
-      <gr-endpoint-param name="someParam" value="yes"></gr-endpoint-param>
-    </gr-endpoint-decorator>
-  </div>`
-);
-
 suite('gr-endpoint-decorator', () => {
-  let container: GrEndpointDecorator;
+  let container: HTMLElement;
 
   let plugin: PluginApi;
   let decorationHook: any;
   let decorationHookWithSlot: any;
   let replacementHook: any;
+  let first: GrEndpointDecorator;
+  let second: GrEndpointDecorator;
+  let banana: GrEndpointDecorator;
 
   setup(async () => {
     resetPlugins();
-    container = basicFixture.instantiate() as GrEndpointDecorator;
+    container = await fixture(
+      html`<div>
+        <gr-endpoint-decorator name="first">
+          <gr-endpoint-param
+            name="first-param"
+            .value=${'barbar'}
+          ></gr-endpoint-param>
+          <p>
+            <span>test slot</span>
+            <gr-endpoint-slot name="test"></gr-endpoint-slot>
+          </p>
+        </gr-endpoint-decorator>
+        <gr-endpoint-decorator name="second">
+          <gr-endpoint-param
+            name="second-param"
+            .value=${'foofoo'}
+          ></gr-endpoint-param>
+        </gr-endpoint-decorator>
+        <gr-endpoint-decorator name="banana">
+          <gr-endpoint-param
+            name="banana-param"
+            .value=${'yes'}
+          ></gr-endpoint-param>
+        </gr-endpoint-decorator>
+      </div>`
+    );
+    first = queryAndAssert<GrEndpointDecorator>(
+      container,
+      'gr-endpoint-decorator[name="first"]'
+    );
+    second = queryAndAssert<GrEndpointDecorator>(
+      container,
+      'gr-endpoint-decorator[name="second"]'
+    );
+    banana = queryAndAssert<GrEndpointDecorator>(
+      container,
+      'gr-endpoint-decorator[name="banana"]'
+    );
+
     window.Gerrit.install(
       p => {
         plugin = p;
@@ -64,18 +79,33 @@
     );
     // Decoration
     decorationHook = plugin.registerCustomComponent('first', 'some-module');
+    const decorationHookPromise = mockPromise();
+    decorationHook.onAttached(() => decorationHookPromise.resolve());
+
+    // Decoration with slot
     decorationHookWithSlot = plugin.registerCustomComponent(
       'first',
       'some-module-2',
       {slot: 'test'}
     );
+    const decorationHookSlotPromise = mockPromise();
+    decorationHookWithSlot.onAttached(() =>
+      decorationHookSlotPromise.resolve()
+    );
+
     // Replacement
     replacementHook = plugin.registerCustomComponent('second', 'other-module', {
       replace: true,
     });
+    const replacementHookPromise = mockPromise();
+    replacementHook.onAttached(() => replacementHookPromise.resolve());
+
     // Mimic all plugins loaded.
     getPluginLoader().loadPlugins([]);
-    await flush();
+
+    await decorationHookPromise;
+    await decorationHookSlotPromise;
+    await replacementHookPromise;
   });
 
   teardown(() => {
@@ -89,17 +119,15 @@
     assert.equal(endpoints.length, 3);
   });
 
-  test('decoration', () => {
-    const element = container.querySelector(
-      'gr-endpoint-decorator[name="first"]'
-    ) as GrEndpointDecorator;
-    const modules = Array.from(element.root!.children).filter(
+  test('first decoration', () => {
+    const element = first;
+    const modules = Array.from(element.children).filter(
       element => element.nodeName === 'SOME-MODULE'
     );
     assert.equal(modules.length, 1);
     const [module] = modules;
     assert.isOk(module);
-    assert.equal((module as any)['someparam'], 'barbar');
+    assert.equal((module as any)['first-param'], 'barbar');
     return decorationHook
       .getLastAttached()
       .then((element: any) => {
@@ -112,14 +140,12 @@
   });
 
   test('decoration with slot', () => {
-    const element = container.querySelector(
-      'gr-endpoint-decorator[name="first"]'
-    ) as GrEndpointDecorator;
+    const element = first;
     const modules = [...element.querySelectorAll('some-module-2')];
     assert.equal(modules.length, 1);
     const [module] = modules;
     assert.isOk(module);
-    assert.equal((module as any)['someparam'], 'barbar');
+    assert.equal((module as any)['first-param'], 'barbar');
     return decorationHookWithSlot
       .getLastAttached()
       .then((element: any) => {
@@ -132,14 +158,12 @@
   });
 
   test('replacement', () => {
-    const element = container.querySelector(
-      'gr-endpoint-decorator[name="second"]'
-    ) as GrEndpointDecorator;
-    const module = Array.from(element.root!.children).find(
+    const element = second;
+    const module = Array.from(element.children).find(
       element => element.nodeName === 'OTHER-MODULE'
     );
     assert.isOk(module);
-    assert.equal((module as any)['someparam'], 'foofoo');
+    assert.equal((module as any)['second-param'], 'foofoo');
     return replacementHook
       .getLastAttached()
       .then((element: any) => {
@@ -152,73 +176,92 @@
   });
 
   test('late registration', async () => {
-    plugin.registerCustomComponent('banana', 'noob-noob');
-    await flush();
-    const element = container.querySelector(
-      'gr-endpoint-decorator[name="banana"]'
-    ) as GrEndpointDecorator;
-    const module = Array.from(element.root!.children).find(
+    const bananaHook = plugin.registerCustomComponent('banana', 'noob-noob');
+    const bananaHookPromise = mockPromise();
+    bananaHook.onAttached(() => bananaHookPromise.resolve());
+    await bananaHookPromise;
+
+    const element = banana;
+    const module = Array.from(element.children).find(
       element => element.nodeName === 'NOOB-NOOB'
     );
     assert.isOk(module);
   });
 
   test('two modules', async () => {
-    plugin.registerCustomComponent('banana', 'mod-one');
-    plugin.registerCustomComponent('banana', 'mod-two');
-    await flush();
-    const element = container.querySelector(
-      'gr-endpoint-decorator[name="banana"]'
-    ) as GrEndpointDecorator;
-    const module1 = Array.from(element.root!.children).find(
+    const bananaHook1 = plugin.registerCustomComponent('banana', 'mod-one');
+    const bananaHookPromise1 = mockPromise();
+    bananaHook1.onAttached(() => bananaHookPromise1.resolve());
+    await bananaHookPromise1;
+
+    const bananaHook = plugin.registerCustomComponent('banana', 'mod-two');
+    const bananaHookPromise2 = mockPromise();
+    bananaHook.onAttached(() => bananaHookPromise2.resolve());
+    await bananaHookPromise2;
+
+    const element = banana;
+    const module1 = Array.from(element.children).find(
       element => element.nodeName === 'MOD-ONE'
     );
     assert.isOk(module1);
-    const module2 = Array.from(element.root!.children).find(
+    const module2 = Array.from(element.children).find(
       element => element.nodeName === 'MOD-TWO'
     );
     assert.isOk(module2);
   });
 
   test('late param setup', async () => {
-    const element = container.querySelector(
-      'gr-endpoint-decorator[name="banana"]'
-    ) as GrEndpointDecorator;
-    const param = element.querySelector('gr-endpoint-param') as GrEndpointParam;
+    let element = banana;
+    const param = queryAndAssert<GrEndpointParam>(element, 'gr-endpoint-param');
     param['value'] = undefined;
-    plugin.registerCustomComponent('banana', 'noob-noob');
-    await flush();
-    let module = Array.from(element.root!.children).find(
+    await param.updateComplete;
+
+    const bananaHook = plugin.registerCustomComponent('banana', 'noob-noob');
+    const bananaHookPromise = mockPromise();
+    bananaHook.onAttached(() => bananaHookPromise.resolve());
+
+    element = queryAndAssert<GrEndpointDecorator>(
+      container,
+      'gr-endpoint-decorator[name="banana"]'
+    );
+    let module = Array.from(element.children).find(
       element => element.nodeName === 'NOOB-NOOB'
     );
     // Module waits for param to be defined.
     assert.isNotOk(module);
     const value = {abc: 'def'};
     param.value = value;
+    await param.updateComplete;
+    await bananaHookPromise;
 
-    await flush();
-    module = Array.from(element.root!.children).find(
+    module = Array.from(element.children).find(
       element => element.nodeName === 'NOOB-NOOB'
     );
     assert.isOk(module);
-    assert.strictEqual((module as any)['someParam'], value);
+    assert.strictEqual((module as any)['banana-param'], value);
   });
 
   test('param is bound', async () => {
-    const element = container.querySelector(
-      'gr-endpoint-decorator[name="banana"]'
-    ) as GrEndpointDecorator;
-    const param = element.querySelector('gr-endpoint-param') as GrEndpointParam;
+    const element = banana;
+    const param = queryAndAssert<GrEndpointParam>(element, 'gr-endpoint-param');
     const value1 = {abc: 'def'};
     const value2 = {def: 'abc'};
     param.value = value1;
-    plugin.registerCustomComponent('banana', 'noob-noob');
-    await flush();
-    const module = Array.from(element.root!.children).find(
+    await param.updateComplete;
+
+    const bananaHook = plugin.registerCustomComponent('banana', 'noob-noob');
+    const bananaHookPromise = mockPromise();
+    bananaHook.onAttached(() => bananaHookPromise.resolve());
+    await bananaHookPromise;
+
+    const module = Array.from(element.children).find(
       element => element.nodeName === 'NOOB-NOOB'
     );
-    assert.strictEqual((module as any)['someParam'], value1);
+    assert.isOk(module);
+    assert.strictEqual((module as any)['banana-param'], value1);
+
     param.value = value2;
-    assert.strictEqual((module as any)['someParam'], value2);
+    await param.updateComplete;
+    assert.strictEqual((module as any)['banana-param'], value2);
   });
 });
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
index ee89c86..5a00c37 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.ts
@@ -1,21 +1,10 @@
 /**
  * @license
- * Copyright (C) 2017 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.
+ * Copyright 2017 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
  */
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {customElement, property} from '@polymer/decorators';
+import {LitElement, PropertyValues} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -24,26 +13,18 @@
 }
 
 @customElement('gr-endpoint-param')
-export class GrEndpointParam extends PolymerElement {
-  @property({type: String, reflectToAttribute: true})
+export class GrEndpointParam extends LitElement {
+  @property({type: String, reflect: true})
   name = '';
 
-  @property({
-    type: Object,
-    notify: true,
-    observer: '_valueChanged',
-  })
+  @property({type: Object})
   value?: unknown;
 
-  _valueChanged(value: unknown) {
-    /* In polymer 2 the following change was made:
-    "Property change notifications (property-changed events) aren't fired when
-    the value changes as a result of a binding from the host"
-    (see https://polymer-library.polymer-project.org/2.0/docs/about_20).
-    To workaround this problem, we fire the event from the observer.
-    In some cases this fire the event twice, but our code is
-    ready for it.
-    */
-    this.dispatchEvent(new CustomEvent('value-changed', {detail: {value}}));
+  override willUpdate(changedProperties: PropertyValues) {
+    if (changedProperties.has('value')) {
+      this.dispatchEvent(
+        new CustomEvent('value-changed', {detail: {value: this.value}})
+      );
+    }
   }
 }
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-slot/gr-endpoint-slot.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-slot/gr-endpoint-slot.ts
index f15b046..d6d1866 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-slot/gr-endpoint-slot.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-slot/gr-endpoint-slot.ts
@@ -1,21 +1,10 @@
 /**
  * @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.
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
  */
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {customElement, property} from '@polymer/decorators';
+import {LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -28,7 +17,7 @@
  * the registered element should appear inside of the endpoint.
  */
 @customElement('gr-endpoint-slot')
-export class GrEndpointSlot extends PolymerElement {
+export class GrEndpointSlot extends LitElement {
   @property({type: String})
   name!: string;
 }
@@ -40,6 +29,6 @@
  * This should help catch errors when you assign an element without
  * name to GrEndpointSlot type.
  */
-export interface GrEndpointSlot extends PolymerElement {
+export interface GrEndpointSlot extends LitElement {
   name: string;
 }