Convert gr-repo-list to lit
Change-Id: I160d92d65b81e516b6b2bf250a268b273f5bebb3
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
index a444067..8e50b5f 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
@@ -14,24 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import '../../../styles/gr-table-styles';
-import '../../../styles/shared-styles';
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-list-view/gr-list-view';
import '../../shared/gr-overlay/gr-overlay';
import '../gr-create-repo-dialog/gr-create-repo-dialog';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-repo-list_html';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {customElement, property, observe, computed} from '@polymer/decorators';
import {AppElementAdminParams} from '../../gr-app-types';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
-import {RepoName, ProjectInfoWithName} from '../../../types/common';
+import {
+ RepoName,
+ ProjectInfoWithName,
+ WebLinkInfo,
+} from '../../../types/common';
import {GrCreateRepoDialog} from '../gr-create-repo-dialog/gr-create-repo-dialog';
import {ProjectState, SHOWN_ITEMS_COUNT} from '../../../constants/constants';
import {fireTitleChange} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
import {encodeURL, getBaseUrl} from '../../../utils/url-util';
+import {tableStyles} from '../../../styles/gr-table-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, PropertyValues, css, html} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators';
declare global {
interface HTMLElementTagNameMap {
@@ -39,154 +42,259 @@
}
}
-export interface GrRepoList {
- $: {
- createOverlay: GrOverlay;
- createNewModal: GrCreateRepoDialog;
- };
-}
-
@customElement('gr-repo-list')
-export class GrRepoList extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
+export class GrRepoList extends LitElement {
+ @query('#createOverlay')
+ createOverlay?: GrOverlay;
+
+ @query('#createNewModal')
+ createNewModal?: GrCreateRepoDialog;
@property({type: Object})
params?: AppElementAdminParams;
- @property({type: Number})
- _offset = 0;
+ @state() offset = 0;
- @property({type: String})
- readonly _path = '/admin/repos';
+ @state() newRepoName = false;
- @property({type: Boolean})
- _newRepoName = false;
+ @state() createNewCapability = false;
- @property({type: Boolean})
- _createNewCapability = false;
+ @state() repos: ProjectInfoWithName[] = [];
- @property({type: Array})
- _repos: ProjectInfoWithName[] = [];
+ @state() reposPerPage = 25;
- @property({type: Number})
- _reposPerPage = 25;
+ @state() loading = true;
- @property({type: Boolean})
- _loading = true;
+ @state() filter = '';
- @property({type: String})
- _filter = '';
-
- @computed('_repos')
- get _shownRepos() {
- return this._repos.slice(0, SHOWN_ITEMS_COUNT);
- }
+ @state() readonly path = '/admin/repos';
private readonly restApiService = appContext.restApiService;
- override connectedCallback() {
+ override async connectedCallback() {
super.connectedCallback();
- this._getCreateRepoCapability();
+ await this.getCreateRepoCapability();
fireTitleChange(this, 'Repos');
- this._maybeOpenCreateOverlay(this.params);
+ this.maybeOpenCreateOverlay(this.params);
}
- @observe('params')
- _paramsChanged(params: AppElementAdminParams) {
- this._loading = true;
- this._filter = params?.filter ?? '';
- this._offset = Number(params?.offset ?? 0);
+ static override get styles() {
+ return [
+ tableStyles,
+ sharedStyles,
+ css`
+ .genericList tr td:last-of-type {
+ text-align: left;
+ }
+ .genericList tr th:last-of-type {
+ text-align: left;
+ }
+ .readOnly {
+ text-align: center;
+ }
+ .changesLink,
+ .name,
+ .repositoryBrowser,
+ .readOnly {
+ white-space: nowrap;
+ }
+ `,
+ ];
+ }
- return this._getRepos(this._filter, this._reposPerPage, this._offset);
+ override render() {
+ return html`
+ <gr-list-view
+ .createNew=${this.createNewCapability}
+ .filter=${this.filter}
+ .itemsPerPage=${this.reposPerPage}
+ .items=${this.repos}
+ .loading=${this.loading}
+ .offset=${this.offset}
+ .path=${this.path}
+ @create-clicked=${this.handleCreateClicked}
+ >
+ <table id="list" class="genericList">
+ <tbody>
+ <tr class="headerRow">
+ <th class="name topHeader">Repository Name</th>
+ <th class="repositoryBrowser topHeader">Repository Browser</th>
+ <th class="changesLink topHeader">Changes</th>
+ <th class="topHeader readOnly">Read only</th>
+ <th class="description topHeader">Repository Description</th>
+ </tr>
+ <tr
+ id="loading"
+ class="loadingMsg ${this.computeLoadingClass(this.loading)}"
+ >
+ <td>Loading...</td>
+ </tr>
+ </tbody>
+ <tbody class="${this.computeLoadingClass(this.loading)}">
+ ${this.renderRepoList()}
+ </tbody>
+ </table>
+ </gr-list-view>
+ <gr-overlay id="createOverlay" with-backdrop>
+ <gr-dialog
+ id="createDialog"
+ class="confirmDialog"
+ ?disabled=${!this.newRepoName}
+ confirm-label="Create"
+ @confirm=${this.handleCreateRepo}
+ @cancel=${this.handleCloseCreate}
+ >
+ <div class="header" slot="header">Create Repository</div>
+ <div class="main" slot="main">
+ <gr-create-repo-dialog
+ id="createNewModal"
+ @new-repo-name=${this.handleNewRepoName}
+ ></gr-create-repo-dialog>
+ </div>
+ </gr-dialog>
+ </gr-overlay>
+ `;
+ }
+
+ private renderRepoList() {
+ const shownRepos = this.repos.slice(0, SHOWN_ITEMS_COUNT);
+ return shownRepos.map(item => this.renderRepo(item));
+ }
+
+ private renderRepo(item: ProjectInfoWithName) {
+ return html`
+ <tr class="table">
+ <td class="name">
+ <a href="${this.computeRepoUrl(item.name)}">${item.name}</a>
+ </td>
+ <td class="repositoryBrowser">${this.renderWebLinks(item)}</td>
+ <td class="changesLink">
+ <a href="${this.computeChangesLink(item.name)}">view all</a>
+ </td>
+ <td class="readOnly">
+ ${item.state === ProjectState.READ_ONLY ? 'Y' : ''}
+ </td>
+ <td class="description">${item.description}</td>
+ </tr>
+ `;
+ }
+
+ private renderWebLinks(links: ProjectInfoWithName) {
+ const webLinks = links.web_links ? links.web_links : [];
+ return webLinks.map(link => this.renderWebLink(link));
+ }
+
+ private renderWebLink(link: WebLinkInfo) {
+ return html`
+ <a href="${link.url}" class="webLink" rel="noopener" target="_blank">
+ ${link.name}
+ </a>
+ `;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('params')) {
+ this._paramsChanged();
+ }
+ }
+
+ async _paramsChanged() {
+ const params = this.params;
+ this.loading = true;
+ this.filter = params?.filter ?? '';
+ this.offset = Number(params?.offset ?? 0);
+
+ return await this.getRepos();
}
/**
- * Opens the create overlay if the route has a hash 'create'
+ * Opens the create overlay if the route has a hash 'create'.
+ *
+ * private but used in test
*/
- _maybeOpenCreateOverlay(params?: AppElementAdminParams) {
+ maybeOpenCreateOverlay(params?: AppElementAdminParams) {
if (params?.openCreateModal) {
- this.$.createOverlay.open();
+ this.createOverlay?.open();
}
}
- _computeRepoUrl(name: string) {
- return getBaseUrl() + this._path + '/' + encodeURL(name, true);
+ private computeRepoUrl(name: string) {
+ return `${getBaseUrl()}${this.path}/${encodeURL(name, true)}`;
}
- _computeChangesLink(name: string) {
+ private computeChangesLink(name: string) {
return GerritNav.getUrlForProjectChanges(name as RepoName);
}
- _getCreateRepoCapability() {
- return this.restApiService.getAccount().then(account => {
- if (!account) {
- return;
- }
- return this.restApiService
- .getAccountCapabilities(['createProject'])
- .then(capabilities => {
- if (capabilities?.createProject) {
- this._createNewCapability = true;
- }
- });
- });
- }
+ private async getCreateRepoCapability() {
+ const account = await this.restApiService.getAccount();
- _getRepos(filter: string, reposPerPage: number, offset?: number) {
- this._repos = [];
- return this.restApiService
- .getRepos(filter, reposPerPage, offset)
- .then(repos => {
- // Late response.
- if (filter !== this._filter || !repos) {
- return;
- }
- this._repos = repos.filter(repo =>
- repo.name.toLowerCase().includes(filter.toLowerCase())
- );
- this._loading = false;
- });
- }
+ if (!account) return;
- _refreshReposList() {
- this.restApiService.invalidateReposCache();
- return this._getRepos(this._filter, this._reposPerPage, this._offset);
- }
-
- async _handleCreateRepo() {
- await this.$.createNewModal.handleCreateRepo();
- this._refreshReposList();
- }
-
- _handleCloseCreate() {
- this.$.createOverlay.close();
- }
-
- _handleCreateClicked() {
- this.$.createOverlay.open().then(() => {
- this.$.createNewModal.focus();
- });
- }
-
- _readOnly(repo: ProjectInfoWithName) {
- return repo.state === ProjectState.READ_ONLY ? 'Y' : '';
- }
-
- _computeWeblink(repo: ProjectInfoWithName) {
- if (!repo.web_links) {
- return '';
+ const accountCapabilities =
+ await this.restApiService.getAccountCapabilities(['createProject']);
+ if (accountCapabilities?.createProject) {
+ this.createNewCapability = true;
}
- const webLinks = repo.web_links;
- return webLinks.length ? webLinks : null;
+
+ return account;
}
+ /* private but used in test */
+ async getRepos() {
+ this.repos = [];
+
+ // We save the filter before getting the repos
+ // and then we check the value hasn't changed aftwards.
+ const filter = this.filter;
+
+ const repos = await this.restApiService.getRepos(
+ this.filter,
+ this.reposPerPage,
+ this.offset
+ );
+
+ // Late response.
+ if (filter !== this.filter || !repos) return;
+
+ this.repos = repos.filter(repo =>
+ repo.name.toLowerCase().includes(filter.toLowerCase())
+ );
+ this.loading = false;
+
+ return repos;
+ }
+
+ private async refreshReposList() {
+ this.restApiService.invalidateReposCache();
+ return await this.getRepos();
+ }
+
+ /* private but used in test */
+ async handleCreateRepo() {
+ await this.createNewModal?.handleCreateRepo();
+ await this.refreshReposList();
+ }
+
+ /* private but used in test */
+ handleCloseCreate() {
+ this.createOverlay?.close();
+ }
+
+ /* private but used in test */
+ handleCreateClicked() {
+ this.createOverlay?.open().then(() => {
+ this.createNewModal?.focus();
+ });
+ }
+
+ /* private but used in test */
computeLoadingClass(loading: boolean) {
return loading ? 'loading' : '';
}
- _handleNewRepoName() {
- this._newRepoName = this.$.createNewModal.nameChanged;
+ private handleNewRepoName() {
+ if (!this.createNewModal) return;
+ this.newRepoName = this.createNewModal.nameChanged;
}
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_html.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_html.ts
deleted file mode 100644
index d65c499..0000000
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_html.ts
+++ /dev/null
@@ -1,116 +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-table-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style>
- .genericList tr td:last-of-type {
- text-align: left;
- }
- .genericList tr th:last-of-type {
- text-align: left;
- }
- .readOnly {
- text-align: center;
- }
- .changesLink,
- .name,
- .repositoryBrowser,
- .readOnly {
- white-space: nowrap;
- }
- </style>
- <gr-list-view
- create-new="[[_createNewCapability]]"
- filter="[[_filter]]"
- items-per-page="[[_reposPerPage]]"
- items="[[_repos]]"
- loading="[[_loading]]"
- offset="[[_offset]]"
- on-create-clicked="_handleCreateClicked"
- path="[[_path]]"
- >
- <table id="list" class="genericList">
- <tbody>
- <tr class="headerRow">
- <th class="name topHeader">Repository Name</th>
- <th class="repositoryBrowser topHeader">Repository Browser</th>
- <th class="changesLink topHeader">Changes</th>
- <th class="topHeader readOnly">Read only</th>
- <th class="description topHeader">Repository Description</th>
- </tr>
- <tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
- <td>Loading...</td>
- </tr>
- </tbody>
- <tbody class$="[[computeLoadingClass(_loading)]]">
- <template is="dom-repeat" items="[[_shownRepos]]">
- <tr class="table">
- <td class="name">
- <a href$="[[_computeRepoUrl(item.name)]]">[[item.name]]</a>
- </td>
- <td class="repositoryBrowser">
- <template
- is="dom-repeat"
- items="[[_computeWeblink(item)]]"
- as="link"
- >
- <a
- href$="[[link.url]]"
- class="webLink"
- rel="noopener"
- target="_blank"
- >
- [[link.name]]
- </a>
- </template>
- </td>
- <td class="changesLink">
- <a href$="[[_computeChangesLink(item.name)]]">view all</a>
- </td>
- <td class="readOnly">[[_readOnly(item)]]</td>
- <td class="description">[[item.description]]</td>
- </tr>
- </template>
- </tbody>
- </table>
- </gr-list-view>
- <gr-overlay id="createOverlay" with-backdrop="">
- <gr-dialog
- id="createDialog"
- class="confirmDialog"
- disabled="[[!_newRepoName]]"
- confirm-label="Create"
- on-confirm="_handleCreateRepo"
- on-cancel="_handleCloseCreate"
- >
- <div class="header" slot="header">Create Repository</div>
- <div class="main" slot="main">
- <gr-create-repo-dialog
- id="createNewModal"
- on-new-repo-name="_handleNewRepoName"
- ></gr-create-repo-dialog>
- </div>
- </gr-dialog>
- </gr-overlay>
-`;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
index 7f7b0cf..142a838 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
@@ -19,14 +19,18 @@
import './gr-repo-list';
import {GrRepoList} from './gr-repo-list';
import {page} from '../../../utils/page-wrapper-utils';
-import {queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {
+ mockPromise,
+ queryAndAssert,
+ stubRestApi,
+} from '../../../test/test-utils';
import {
UrlEncodedRepoName,
ProjectInfoWithName,
RepoName,
} from '../../../types/common';
import {AppElementAdminParams} from '../../gr-app-types';
-import {ProjectState} from '../../../constants/constants';
+import {ProjectState, SHOWN_ITEMS_COUNT} from '../../../constants/constants';
import {GerritView} from '../../../services/router/router-model';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
@@ -60,47 +64,46 @@
let element: GrRepoList;
let repos: ProjectInfoWithName[];
- const value: AppElementAdminParams = {view: GerritView.ADMIN, adminView: ''};
-
- setup(() => {
+ setup(async () => {
sinon.stub(page, 'show');
element = basicFixture.instantiate();
+ await element.updateComplete;
});
suite('list with repos', () => {
setup(async () => {
repos = createRepoList('test', 26);
stubRestApi('getRepos').returns(Promise.resolve(repos));
- await element._paramsChanged(value);
- await flush();
+ await element._paramsChanged();
+ await element.updateComplete;
});
test('test for test repo in the list', async () => {
- await flush();
- assert.equal(element._repos[0].id, 'test0');
- assert.equal(element._repos[1].id, 'test1');
- assert.equal(element._repos[2].id, 'test2');
+ await element.updateComplete;
+ assert.equal(element.repos[0].id, 'test0');
+ assert.equal(element.repos[1].id, 'test1');
+ assert.equal(element.repos[2].id, 'test2');
});
- test('_shownRepos', () => {
- assert.equal(element._shownRepos.length, 25);
+ test('shownRepos', () => {
+ assert.equal(element.repos.slice(0, SHOWN_ITEMS_COUNT).length, 25);
});
- test('_maybeOpenCreateOverlay', () => {
+ test('maybeOpenCreateOverlay', () => {
const overlayOpen = sinon.stub(
queryAndAssert<GrOverlay>(element, '#createOverlay'),
'open'
);
- element._maybeOpenCreateOverlay();
+ element.maybeOpenCreateOverlay();
assert.isFalse(overlayOpen.called);
- element._maybeOpenCreateOverlay(undefined);
+ element.maybeOpenCreateOverlay(undefined);
assert.isFalse(overlayOpen.called);
const params: AppElementAdminParams = {
view: GerritView.ADMIN,
adminView: '',
openCreateModal: true,
};
- element._maybeOpenCreateOverlay(params);
+ element.maybeOpenCreateOverlay(params);
assert.isTrue(overlayOpen.called);
});
});
@@ -109,12 +112,12 @@
setup(async () => {
repos = createRepoList('test', 25);
stubRestApi('getRepos').returns(Promise.resolve(repos));
- await element._paramsChanged(value);
- await flush();
+ await element._paramsChanged();
+ await element.updateComplete;
});
- test('_shownRepos', () => {
- assert.equal(element._shownRepos.length, 25);
+ test('shownRepos', () => {
+ assert.equal(element.repos.slice(0, SHOWN_ITEMS_COUNT).length, 25);
});
});
@@ -129,41 +132,51 @@
test('_paramsChanged', async () => {
const repoStub = stubRestApi('getRepos');
repoStub.returns(Promise.resolve(repos));
- const value: AppElementAdminParams = {
+ element.params = {
view: GerritView.ADMIN,
adminView: '',
filter: 'test',
offset: 25,
- };
- await element._paramsChanged(value);
+ } as AppElementAdminParams;
+ await element._paramsChanged();
assert.isTrue(repoStub.lastCall.calledWithExactly('test', 25, 25));
});
test('latest repos requested are always set', async () => {
const repoStub = stubRestApi('getRepos');
- repoStub.withArgs('test', 25).returns(Promise.resolve(repos));
- repoStub.withArgs('filter', 25).returns(Promise.resolve(reposFiltered));
- element._filter = 'test';
+ const promise = mockPromise<ProjectInfoWithName[]>();
+ repoStub.withArgs('filter', 25).returns(promise);
- // Repos are not set because the element._filter differs.
- await element._getRepos('filter', 25, 0);
- assert.deepEqual(element._repos, []);
+ element.filter = 'test';
+ element.reposPerPage = 25;
+ element.offset = 0;
+
+ // Repos are not set because the element.filter differs.
+ const p = element.getRepos();
+ element.filter = 'filter';
+ promise.resolve(reposFiltered);
+ await p;
+ assert.deepEqual(element.repos, []);
});
test('filter is case insensitive', async () => {
const repoStub = stubRestApi('getRepos');
const repos = [createRepo('aSDf', 0)];
repoStub.withArgs('asdf', 25).returns(Promise.resolve(repos));
- element._filter = 'asdf';
- await element._getRepos('asdf', 25, 0);
- assert.equal(element._repos.length, 1);
+
+ element.filter = 'asdf';
+ element.reposPerPage = 25;
+ element.offset = 0;
+
+ await element.getRepos();
+ assert.equal(element.repos.length, 1);
});
});
suite('loading', () => {
- test('correct contents are displayed', () => {
- assert.isTrue(element._loading);
- assert.equal(element.computeLoadingClass(element._loading), 'loading');
+ test('correct contents are displayed', async () => {
+ assert.isTrue(element.loading);
+ assert.equal(element.computeLoadingClass(element.loading), 'loading');
assert.equal(
getComputedStyle(
queryAndAssert<HTMLTableRowElement>(element, '#loading')
@@ -171,11 +184,11 @@
'block'
);
- element._loading = false;
- element._repos = createRepoList('test', 25);
+ element.loading = false;
+ element.repos = createRepoList('test', 25);
- flush();
- assert.equal(element.computeLoadingClass(element._loading), '');
+ await element.updateComplete;
+ assert.equal(element.computeLoadingClass(element.loading), '');
assert.equal(
getComputedStyle(
queryAndAssert<HTMLTableRowElement>(element, '#loading')
@@ -186,11 +199,9 @@
});
suite('create new', () => {
- test('_handleCreateClicked called when create-click fired', () => {
- const handleCreateClickedStub = sinon.stub(
- element,
- '_handleCreateClicked'
- );
+ test('handleCreateClicked called when create-clicked fired', () => {
+ const handleCreateClickedStub = sinon.stub();
+ element.addEventListener('create-clicked', handleCreateClickedStub);
queryAndAssert<GrListView>(element, 'gr-list-view').dispatchEvent(
new CustomEvent('create-clicked', {
composed: true,
@@ -200,31 +211,33 @@
assert.isTrue(handleCreateClickedStub.called);
});
- test('_handleCreateClicked opens modal', () => {
+ test('handleCreateClicked opens modal', () => {
const openStub = sinon
.stub(queryAndAssert<GrOverlay>(element, '#createOverlay'), 'open')
.returns(Promise.resolve());
- element._handleCreateClicked();
+ element.handleCreateClicked();
assert.isTrue(openStub.called);
});
- test('_handleCreateRepo called when confirm fired', () => {
- const handleCreateRepoStub = sinon.stub(element, '_handleCreateRepo');
+ test('handleCreateRepo called when confirm fired', () => {
+ const handleCreateRepoStub = sinon.stub();
+ element.addEventListener('confirm', handleCreateRepoStub);
queryAndAssert<GrDialog>(element, '#createDialog').dispatchEvent(
new CustomEvent('confirm', {
composed: true,
- bubbles: true,
+ bubbles: false,
})
);
assert.isTrue(handleCreateRepoStub.called);
});
- test('_handleCloseCreate called when cancel fired', () => {
- const handleCloseCreateStub = sinon.stub(element, '_handleCloseCreate');
+ test('handleCloseCreate called when cancel fired', () => {
+ const handleCloseCreateStub = sinon.stub();
+ element.addEventListener('cancel', handleCloseCreateStub);
queryAndAssert<GrDialog>(element, '#createDialog').dispatchEvent(
new CustomEvent('cancel', {
composed: true,
- bubbles: true,
+ bubbles: false,
})
);
assert.isTrue(handleCloseCreateStub.called);
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 3ac7c7b..4406a73 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -206,6 +206,7 @@
UrlEncodedRepoName,
UserConfigInfo,
VotingRangeInfo,
+ WebLinkInfo,
isDetailedLabelInfo,
isQuickLabelInfo,
};