Migrate gr-plugin-config-array-editor to lit
Release-Notes: skip
Change-Id: I8767940bcbd6e2b404329ae5cbeee6e99ad513f0
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
index 55f7567..2033180 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.ts
@@ -14,19 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import '@polymer/iron-input/iron-input';
import '@polymer/paper-toggle-button/paper-toggle-button';
-import '../../../styles/gr-form-styles';
-import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
-import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-plugin-config-array-editor_html';
-import {property, customElement} from '@polymer/decorators';
import {
PluginConfigOptionsChangedEventDetail,
ArrayPluginOption,
} from '../gr-repo-plugin-config/gr-repo-plugin-config-types';
+import {formStyles} from '../../../styles/gr-form-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, html, css} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+import {BindValueChangeEvent} from '../../../types/events';
declare global {
interface HTMLElementTagNameMap {
@@ -35,61 +35,153 @@
}
@customElement('gr-plugin-config-array-editor')
-export class GrPluginConfigArrayEditor extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrPluginConfigArrayEditor extends LitElement {
/**
* Fired when the plugin config option changes.
*
* @event plugin-config-option-changed
*/
- @property({type: String})
- _newValue = '';
+ // private but used in test
+ @state() newValue = '';
// This property is never null, since this component in only about operations
// on pluginOption.
@property({type: Object})
pluginOption!: ArrayPluginOption;
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true})
disabled = false;
- _handleAddTap(e: MouseEvent) {
- e.preventDefault();
- this._handleAdd();
+ static override get styles() {
+ return [
+ sharedStyles,
+ formStyles,
+ css`
+ .wrapper {
+ width: 30em;
+ }
+ .existingItems {
+ background: var(--table-header-background-color);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ }
+ gr-button {
+ float: right;
+ margin-left: var(--spacing-m);
+ width: 4.5em;
+ }
+ .row {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ padding: var(--spacing-m) 0;
+ width: 100%;
+ }
+ .existingItems .row {
+ padding: var(--spacing-m);
+ }
+ .existingItems .row:not(:first-of-type) {
+ border-top: 1px solid var(--border-color);
+ }
+ input {
+ flex-grow: 1;
+ }
+ .hide {
+ display: none;
+ }
+ .placeholder {
+ color: var(--deemphasized-text-color);
+ padding-top: var(--spacing-m);
+ }
+ `,
+ ];
}
- _handleInputKeydown(e: KeyboardEvent) {
+ override render() {
+ return html`
+ <div class="wrapper gr-form-styles">
+ ${this.renderPluginOptions()}
+ <div class="row ${this.disabled ? 'hide' : ''}">
+ <iron-input
+ .bindValue=${this.newValue}
+ @bind-value-changed=${this.handleBindValueChangedNewValue}
+ >
+ <input
+ id="input"
+ @keydown=${this.handleInputKeydown}
+ ?disabled=${this.disabled}
+ />
+ </iron-input>
+ <gr-button
+ id="addButton"
+ ?disabled=${!this.newValue.length}
+ link
+ @click=${this.handleAddTap}
+ >Add</gr-button
+ >
+ </div>
+ </div>
+ `;
+ }
+
+ private renderPluginOptions() {
+ if (!this.pluginOption?.info?.values?.length) {
+ return html`<div class="row placeholder">None configured.</div>`;
+ }
+
+ return html`
+ <div class="existingItems">
+ ${this.pluginOption.info.values.map(item =>
+ this.renderPluginOptionValue(item)
+ )}
+ </div>
+ `;
+ }
+
+ private renderPluginOptionValue(item: string) {
+ return html`
+ <div class="row">
+ <span>${item}</span>
+ <gr-button
+ link
+ ?disabled=${this.disabled}
+ @click=${() => this.handleDelete(item)}
+ >Delete</gr-button
+ >
+ </div>
+ `;
+ }
+
+ private handleAddTap(e: MouseEvent) {
+ e.preventDefault();
+ this.handleAdd();
+ }
+
+ private handleInputKeydown(e: KeyboardEvent) {
// Enter.
if (e.keyCode === 13) {
e.preventDefault();
- this._handleAdd();
+ this.handleAdd();
}
}
- _handleAdd() {
- if (!this._newValue.length) {
+ private handleAdd() {
+ if (!this.newValue.length) {
return;
}
- this._dispatchChanged(
- this.pluginOption.info.values.concat([this._newValue])
- );
- this._newValue = '';
+ this.dispatchChanged(this.pluginOption.info.values.concat([this.newValue]));
+ this.newValue = '';
}
- _handleDelete(e: MouseEvent) {
- const value = ((dom(e) as EventApi).localTarget as HTMLElement).dataset[
- 'item'
- ];
- this._dispatchChanged(
+ private handleDelete(value: string) {
+ this.dispatchChanged(
this.pluginOption.info.values.filter(str => str !== value)
);
}
- _dispatchChanged(values: string[]) {
+ // private but used in test
+ dispatchChanged(values: string[]) {
const {_key, info} = this.pluginOption;
const detail: PluginConfigOptionsChangedEventDetail = {
_key,
@@ -101,7 +193,7 @@
);
}
- _computeShowInputRow(disabled: boolean) {
- return disabled ? 'hide' : '';
+ private handleBindValueChangedNewValue(e: BindValueChangeEvent) {
+ this.newValue = e.detail.value;
}
}
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_html.ts b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_html.ts
deleted file mode 100644
index 7709198..0000000
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_html.ts
+++ /dev/null
@@ -1,100 +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">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="gr-form-styles">
- .wrapper {
- width: 30em;
- }
- .existingItems {
- background: var(--table-header-background-color);
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- }
- gr-button {
- float: right;
- margin-left: var(--spacing-m);
- width: 4.5em;
- }
- .row {
- align-items: center;
- display: flex;
- justify-content: space-between;
- padding: var(--spacing-m) 0;
- width: 100%;
- }
- .existingItems .row {
- padding: var(--spacing-m);
- }
- .existingItems .row:not(:first-of-type) {
- border-top: 1px solid var(--border-color);
- }
- input {
- flex-grow: 1;
- }
- .hide {
- display: none;
- }
- .placeholder {
- color: var(--deemphasized-text-color);
- padding-top: var(--spacing-m);
- }
- </style>
- <div class="wrapper gr-form-styles">
- <template is="dom-if" if="[[pluginOption.info.values.length]]">
- <div class="existingItems">
- <template is="dom-repeat" items="[[pluginOption.info.values]]">
- <div class="row">
- <span>[[item]]</span>
- <gr-button
- link=""
- disabled$="[[disabled]]"
- data-item$="[[item]]"
- on-click="_handleDelete"
- >Delete</gr-button
- >
- </div>
- </template>
- </div>
- </template>
- <template is="dom-if" if="[[!pluginOption.info.values.length]]">
- <div class="row placeholder">None configured.</div>
- </template>
- <div class$="row [[_computeShowInputRow(disabled)]]">
- <iron-input on-keydown="_handleInputKeydown" bind-value="{{_newValue}}">
- <input
- is="iron-input"
- id="input"
- on-keydown="_handleInputKeydown"
- bind-value="{{_newValue}}"
- disabled$="[[disabled]]"
- />
- </iron-input>
- <gr-button
- id="addButton"
- disabled$="[[!_newValue.length]]"
- link=""
- on-click="_handleAddTap"
- >Add</gr-button
- >
- </div>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.ts b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.ts
index 655eb6b..5de1f1e 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.ts
@@ -42,80 +42,96 @@
};
});
- test('_computeShowInputRow', () => {
- assert.equal(element._computeShowInputRow(true), 'hide');
- assert.equal(element._computeShowInputRow(false), '');
- });
-
suite('adding', () => {
setup(() => {
- dispatchStub = sinon.stub(element, '_dispatchChanged');
+ dispatchStub = sinon.stub(element, 'dispatchChanged');
});
- test('with enter', () => {
- element._newValue = '';
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
- assert.isFalse(element.$.input.hasAttribute('disabled'));
- flush();
+ test('with enter', async () => {
+ element.newValue = '';
+ await element.updateComplete;
+ MockInteractions.pressAndReleaseKeyOn(
+ queryAndAssert<HTMLInputElement>(element, '#input'),
+ 13
+ ); // Enter
+ await element.updateComplete;
+ assert.isFalse(
+ queryAndAssert<HTMLInputElement>(element, '#input').hasAttribute(
+ 'disabled'
+ )
+ );
+ await element.updateComplete;
assert.isFalse(dispatchStub.called);
- element._newValue = 'test';
- MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
- assert.isFalse(element.$.input.hasAttribute('disabled'));
- flush();
+ element.newValue = 'test';
+ await element.updateComplete;
+
+ MockInteractions.pressAndReleaseKeyOn(
+ queryAndAssert<HTMLInputElement>(element, '#input'),
+ 13
+ ); // Enter
+ await element.updateComplete;
+ assert.isFalse(
+ queryAndAssert<HTMLInputElement>(element, '#input').hasAttribute(
+ 'disabled'
+ )
+ );
+ await element.updateComplete;
assert.isTrue(dispatchStub.called);
assert.equal(dispatchStub.lastCall.args[0], 'test');
- assert.equal(element._newValue, '');
+ assert.equal(element.newValue, '');
});
- test('with add btn', () => {
- element._newValue = '';
- MockInteractions.tap(element.$.addButton);
- flush();
+ test('with add btn', async () => {
+ element.newValue = '';
+ queryAndAssert<GrButton>(element, '#addButton').click();
+ await element.updateComplete;
assert.isFalse(dispatchStub.called);
- element._newValue = 'test';
- MockInteractions.tap(element.$.addButton);
- flush();
+
+ element.newValue = 'test';
+ await element.updateComplete;
+
+ queryAndAssert<GrButton>(element, '#addButton').click();
+ await element.updateComplete;
assert.isTrue(dispatchStub.called);
assert.equal(dispatchStub.lastCall.args[0], 'test');
- assert.equal(element._newValue, '');
+ assert.equal(element.newValue, '');
});
});
test('deleting', async () => {
- dispatchStub = sinon.stub(element, '_dispatchChanged');
+ dispatchStub = sinon.stub(element, 'dispatchChanged');
element.pluginOption = {
_key: '',
info: {type: ConfigParameterInfoType.ARRAY, values: ['test', 'test2']},
};
element.disabled = true;
- await flush();
+ await element.updateComplete;
const rows = queryAll(element, '.existingItems .row');
assert.equal(rows.length, 2);
const button = queryAndAssert<GrButton>(rows[0], 'gr-button');
MockInteractions.tap(button);
- await flush();
+ await element.updateComplete;
assert.isFalse(dispatchStub.called);
element.disabled = false;
- element.notifyPath('pluginOption.info.editable');
- await flush();
+ await element.updateComplete;
- MockInteractions.tap(button);
- await flush();
+ button.click();
+ await element.updateComplete;
assert.isTrue(dispatchStub.called);
assert.deepEqual(dispatchStub.lastCall.args[0], ['test2']);
});
- test('_dispatchChanged', () => {
+ test('dispatchChanged', () => {
const eventStub = sinon.stub(element, 'dispatchEvent');
- element._dispatchChanged(['new-test-value']);
+ element.dispatchChanged(['new-test-value']);
assert.isTrue(eventStub.called);
const {detail} = eventStub.lastCall.args[0] as CustomEvent;