Merge "Fix link for patchset level comment"
diff --git a/java/com/google/gerrit/server/patch/PatchListLoader.java b/java/com/google/gerrit/server/patch/PatchListLoader.java
index c3d9a1d..be0895b 100644
--- a/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -26,10 +26,12 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -41,6 +43,7 @@
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@@ -59,6 +62,7 @@
import org.eclipse.jgit.diff.HistogramDiff;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
@@ -383,8 +387,20 @@
Set<ContextAwareEdit> editsDueToRebase)
throws IOException {
FileHeader fileHeader = toFileHeader(key.getNewId(), diffFormatter, diffEntry);
- long oldSize = getFileSize(objectReader, diffEntry.getOldMode(), diffEntry.getOldPath(), treeA);
- long newSize = getFileSize(objectReader, diffEntry.getNewMode(), diffEntry.getNewPath(), treeB);
+ long oldSize =
+ getFileSize(
+ objectReader,
+ diffEntry.getOldId(),
+ diffEntry.getOldMode(),
+ diffEntry.getOldPath(),
+ treeA);
+ long newSize =
+ getFileSize(
+ objectReader,
+ diffEntry.getNewId(),
+ diffEntry.getNewMode(),
+ diffEntry.getNewPath(),
+ treeB);
Set<Edit> contentEditsDueToRebase = getContentEdits(editsDueToRebase);
PatchListEntry patchListEntry =
newEntry(treeA, fileHeader, contentEditsDueToRebase, newSize, newSize - oldSize);
@@ -417,14 +433,18 @@
return ComparisonType.againstOtherPatchSet();
}
- private static long getFileSize(ObjectReader reader, FileMode mode, String path, RevTree t)
+ private static long getFileSize(
+ ObjectReader reader, AbbreviatedObjectId abbreviatedId, FileMode mode, String path, RevTree t)
throws IOException {
if (!isBlob(mode)) {
return 0;
}
- try (TreeWalk tw = TreeWalk.forPath(reader, path, t)) {
- return tw != null ? reader.open(tw.getObjectId(0), OBJ_BLOB).getSize() : 0;
+ ObjectId fileId =
+ toObjectId(reader, abbreviatedId).orElseGet(() -> lookupObjectId(reader, path, t));
+ if (ObjectId.zeroId().equals(fileId)) {
+ return 0;
}
+ return reader.getObjectSize(fileId, OBJ_BLOB);
}
private static boolean isBlob(FileMode mode) {
@@ -432,6 +452,37 @@
return t == FileMode.TYPE_FILE || t == FileMode.TYPE_SYMLINK;
}
+ private static Optional<ObjectId> toObjectId(
+ ObjectReader reader, AbbreviatedObjectId abbreviatedId) throws IOException {
+ if (abbreviatedId == null) {
+ // In theory, DiffEntry#getOldId or DiffEntry#getNewId can be null for pure renames or pure
+ // mode changes (e.g. DiffEntry#modify doesn't set the IDs). However, the method we call
+ // for diffs (DiffFormatter#scan) seems to always produce DiffEntries with set IDs, even for
+ // pure renames.
+ return Optional.empty();
+ }
+ if (abbreviatedId.isComplete()) {
+ // With the current JGit version and the method we call for diffs (DiffFormatter#scan), this
+ // is the only code path taken right now.
+ return Optional.ofNullable(abbreviatedId.toObjectId());
+ }
+ Collection<ObjectId> objectIds = reader.resolve(abbreviatedId);
+ // It seems very unlikely that an ObjectId which was just abbreviated by the diff computation
+ // now can't be resolved to exactly one ObjectId. The API allows this possibility, though.
+ return objectIds.size() == 1
+ ? Optional.of(Iterables.getOnlyElement(objectIds))
+ : Optional.empty();
+ }
+
+ private static ObjectId lookupObjectId(ObjectReader reader, String path, RevTree tree) {
+ // This variant is very expensive.
+ try (TreeWalk treeWalk = TreeWalk.forPath(reader, path, tree)) {
+ return treeWalk != null ? treeWalk.getObjectId(0) : ObjectId.zeroId();
+ } catch (IOException e) {
+ throw new StorageException(e);
+ }
+ }
+
private FileHeader toFileHeader(
ObjectId commitB, DiffFormatter diffFormatter, DiffEntry diffEntry) throws IOException {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
index 172e444..fe0298a 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
@@ -37,6 +37,7 @@
import {URLEncodingBehavior} from '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
import {RESTClientBehavior} from '../../../behaviors/rest-client-behavior/rest-client-behavior.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
+import {GrDisplayNameUtils} from '../../../scripts/gr-display-name-utils/gr-display-name-utils.js';
import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
@@ -47,6 +48,9 @@
LARGE: 1000,
};
+// How many reviewers should be shown with an account-label?
+const PRIMARY_REVIEWERS_COUNT = 2;
+
/**
* @appliesMixin RESTClientMixin
* @extends PolymerElement
@@ -73,6 +77,7 @@
/** @type {?} */
change: Object,
+ config: Object,
changeURL: {
type: String,
computed: '_computeChangeURL(change)',
@@ -213,6 +218,45 @@
}
}
+ _hasAttention(account) {
+ if (!this.change || !this.change.attention_set) return false;
+ return this.change.attention_set.hasOwnProperty(account._account_id);
+ }
+
+ /**
+ * Computes the array of all reviewers with sorting the reviewers first
+ * that are in the attention set.
+ */
+ _computeReviewers(change) {
+ if (!change || !change.reviewers || !change.reviewers.REVIEWER) return [];
+ const reviewers = [...change.reviewers.REVIEWER];
+ reviewers.sort((r1, r2) => {
+ if (this._hasAttention(r1)) return -1;
+ if (this._hasAttention(r2)) return 1;
+ return 0;
+ });
+ return reviewers;
+ }
+
+ _computePrimaryReviewers(change) {
+ return this._computeReviewers(change).slice(0, PRIMARY_REVIEWERS_COUNT);
+ }
+
+ _computeAdditionalReviewers(change) {
+ return this._computeReviewers(change).slice(PRIMARY_REVIEWERS_COUNT);
+ }
+
+ _computeAdditionalReviewersCount(change) {
+ return this._computeAdditionalReviewers(change).length;
+ }
+
+ _computeAdditionalReviewersTitle(change, config) {
+ if (!change || !config) return '';
+ return this._computeAdditionalReviewers(change)
+ .map(user => GrDisplayNameUtils.getDisplayName(config, user))
+ .join(', ');
+ }
+
_computeComments(unresolved_comment_count) {
if (!unresolved_comment_count || unresolved_comment_count < 1) return '';
return `${unresolved_comment_count} unresolved`;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.js
index 36f84f7..d2ab2c6 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.js
@@ -50,6 +50,9 @@
.reviewers {
white-space: nowrap;
}
+ .reviewers {
+ --account-max-length: 75px;
+ }
.spacer {
height: 0;
overflow: hidden;
@@ -150,7 +153,11 @@
class="cell owner"
hidden$="[[isColumnHidden('Owner', visibleChangeTableColumns)]]"
>
- <gr-account-link account="[[change.owner]]"></gr-account-link>
+ <gr-account-link
+ show-attention
+ change="[[change]]"
+ account="[[change.owner]]"
+ ></gr-account-link>
</td>
<td
class="cell assignee"
@@ -173,16 +180,22 @@
<div>
<template
is="dom-repeat"
- items="[[change.reviewers.REVIEWER]]"
+ items="[[_computePrimaryReviewers(change)]]"
as="reviewer"
>
<gr-account-link
hide-avatar=""
hide-status=""
+ show-attention
+ change="[[change]]"
account="[[reviewer]]"
></gr-account-link
- ><!--
- --><span class="lastChildHidden">, </span>
+ ><span class="lastChildHidden">, </span>
+ </template>
+ <template is="dom-if" if="[[_computeAdditionalReviewersCount(change)]]">
+ <span title="[[_computeAdditionalReviewersTitle(change, config)]]">
+ +[[_computeAdditionalReviewersCount(change, config)]]
+ </span>
</template>
</div>
</td>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.js
index 2e50cd8..4802773 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.js
@@ -135,6 +135,7 @@
highlight$="[[_computeItemHighlight(account, change)]]"
needs-review$="[[_computeItemNeedsReview(account, change, showReviewedState)]]"
change="[[change]]"
+ config="[[_config]]"
visible-change-table-columns="[[visibleChangeTableColumns]]"
show-number="[[showNumber]]"
show-star="[[showStar]]"
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index 1cc2316..c5d50c1 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -182,10 +182,14 @@
const {project, dashboard, title, user, sections} = this.params;
const dashboardPromise = project ?
this._getProjectDashboard(project, dashboard) :
- Promise.resolve(GerritNav.getUserDashboard(
- user,
- sections,
- title || this._computeTitle(user)));
+ this.$.restAPI.getConfig().then(
+ config => Promise.resolve(GerritNav.getUserDashboard(
+ user,
+ sections,
+ title || this._computeTitle(user),
+ config
+ ))
+ );
const checkForNewUser = !project && user === 'self';
return dashboardPromise
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
deleted file mode 100644
index d08f529..0000000
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
+++ /dev/null
@@ -1,183 +0,0 @@
-<!DOCTYPE html>
-<!--
-@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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<meta charset="utf-8">
-<title>gr-change-metadata</title>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/components/wct-browser-legacy/browser.js"></script>
-
-<test-fixture id="element">
- <template>
- <gr-change-metadata mutable="true"></gr-change-metadata>
- </template>
-</test-fixture>
-
-<test-fixture id="plugin-host">
- <template>
- <gr-plugin-host></gr-plugin-host>
- </template>
-</test-fixture>
-
-<script type="module">
-import '../../../test/common-test-setup.js';
-import '../../plugins/gr-plugin-host/gr-plugin-host.js';
-import './gr-change-metadata.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {resetPlugins} from '../../../test/test-utils.js';
-import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
-
-const pluginApi = _testOnly_initGerritPluginApi();
-
-suite('gr-change-metadata integration tests', () => {
- let sandbox;
- let element;
-
- const sectionSelectors = [
- 'section.strategy',
- 'section.topic',
- ];
-
- const labels = {
- CI: {
- all: [
- {value: 1, name: 'user 2', _account_id: 1},
- {value: 2, name: 'user '},
- ],
- values: {
- ' 0': 'Don\'t submit as-is',
- '+1': 'No score',
- '+2': 'Looks good to me',
- },
- },
- };
-
- const getStyle = function(selector, name) {
- return window.getComputedStyle(
- dom(element.root).querySelector(selector))[name];
- };
-
- function createElement() {
- const element = fixture('element');
- element.change = {labels, status: 'NEW'};
- element.revision = {};
- return element;
- }
-
- setup(() => {
- sandbox = sinon.sandbox.create();
- stub('gr-rest-api-interface', {
- getConfig() { return Promise.resolve({}); },
- getLoggedIn() { return Promise.resolve(false); },
- deleteVote() { return Promise.resolve({ok: true}); },
- });
- });
-
- teardown(() => {
- sandbox.restore();
- resetPlugins();
- });
-
- suite('by default', () => {
- setup(done => {
- element = createElement();
- flush(done);
- });
-
- for (const sectionSelector of sectionSelectors) {
- test(sectionSelector + ' does not have display: none', () => {
- assert.notEqual(getStyle(sectionSelector, 'display'), 'none');
- });
- }
- });
-
- suite('with plugin style', () => {
- setup(done => {
- resetPlugins();
- const pluginHost = fixture('plugin-host');
- pluginHost.config = {
- plugin: {
- js_resource_paths: [],
- html_resource_paths: [
- new URL('test/plugin.html?' + Math.random(),
- window.location.href).toString(),
- ],
- },
- };
- element = createElement();
- const importSpy = sandbox.spy(element.$.externalStyle, '_import');
- pluginLoader.awaitPluginsLoaded().then(() => {
- Promise.all(importSpy.returnValues).then(() => {
- flush(done);
- });
- });
- });
-
- for (const sectionSelector of sectionSelectors) {
- test(sectionSelector + ' may have display: none', () => {
- assert.equal(getStyle(sectionSelector, 'display'), 'none');
- });
- }
- });
-
- suite('label updates', () => {
- let plugin;
-
- setup(() => {
- pluginApi.install(p => plugin = p, '0.1',
- new URL('test/plugin.html?' + Math.random(),
- window.location.href).toString());
- sandbox.stub(pluginLoader, 'arePluginsLoaded').returns(true);
- pluginLoader.loadPlugins([]);
- element = createElement();
- });
-
- test('labels changed callback', done => {
- let callCount = 0;
- const labelChangeSpy = sandbox.spy(arg => {
- callCount++;
- if (callCount === 1) {
- assert.deepEqual(arg, labels);
- assert.equal(arg.CI.all.length, 2);
- element.set(['change', 'labels'], {
- CI: {
- all: [
- {value: 1, name: 'user 2', _account_id: 1},
- ],
- values: {
- ' 0': 'Don\'t submit as-is',
- '+1': 'No score',
- '+2': 'Looks good to me',
- },
- },
- });
- } else if (callCount === 2) {
- assert.equal(arg.CI.all.length, 1);
- done();
- }
- });
-
- plugin.changeMetadata().onLabelsChanged(labelChangeSpy);
- });
- });
-});
-</script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js
new file mode 100644
index 0000000..d98bac7
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.js
@@ -0,0 +1,180 @@
+/**
+ * @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 '../../../test/common-test-setup-karma.js';
+import {html} from '@polymer/polymer/lib/utils/html-tag.js';
+import './gr-change-metadata.js';
+import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
+import {resetPlugins} from '../../../test/test-utils.js';
+import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
+import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
+
+const testHtmlPlugin = document.createElement('dom-module');
+testHtmlPlugin.innerHTML = `
+ <template>
+ <style>
+ html {
+ --change-metadata-assignee: {
+ display: none;
+ }
+ --change-metadata-label-status: {
+ display: none;
+ }
+ --change-metadata-strategy: {
+ display: none;
+ }
+ --change-metadata-topic: {
+ display: none;
+ }
+ }
+ </style>
+ </template>
+ `;
+testHtmlPlugin.register('my-plugin-style');
+
+const basicFixture = fixtureFromTemplate(
+ html`<gr-change-metadata mutable="true"></gr-change-metadata>`
+);
+
+const pluginApi = _testOnly_initGerritPluginApi();
+
+suite('gr-change-metadata integration tests', () => {
+ let sandbox;
+ let element;
+
+ const sectionSelectors = [
+ 'section.strategy',
+ 'section.topic',
+ ];
+
+ const labels = {
+ CI: {
+ all: [
+ {value: 1, name: 'user 2', _account_id: 1},
+ {value: 2, name: 'user '},
+ ],
+ values: {
+ ' 0': 'Don\'t submit as-is',
+ '+1': 'No score',
+ '+2': 'Looks good to me',
+ },
+ },
+ };
+
+ const getStyle = function(selector, name) {
+ return window.getComputedStyle(
+ dom(element.root).querySelector(selector))[name];
+ };
+
+ function createElement() {
+ const element = basicFixture.instantiate();
+ element.change = {labels, status: 'NEW'};
+ element.revision = {};
+ return element;
+ }
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({}); },
+ getLoggedIn() { return Promise.resolve(false); },
+ deleteVote() { return Promise.resolve({ok: true}); },
+ });
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ resetPlugins();
+ });
+
+ suite('by default', () => {
+ setup(done => {
+ element = createElement();
+ flush(done);
+ });
+
+ for (const sectionSelector of sectionSelectors) {
+ test(sectionSelector + ' does not have display: none', () => {
+ assert.notEqual(getStyle(sectionSelector, 'display'), 'none');
+ });
+ }
+ });
+
+ suite('with plugin style', () => {
+ setup(done => {
+ resetPlugins();
+ pluginApi.install(plugin => {
+ plugin.registerStyleModule('change-metadata', 'my-plugin-style');
+ }, undefined, 'http://test.com/style.js');
+ element = createElement();
+ sandbox.stub(pluginEndpoints, 'importUrl', url => Promise.resolve());
+ pluginLoader.loadPlugins([]);
+ pluginLoader.awaitPluginsLoaded().then(() => {
+ flush(done);
+ });
+ });
+
+ for (const sectionSelector of sectionSelectors) {
+ test('section.strategy may have display: none', () => {
+ assert.equal(getStyle(sectionSelector, 'display'), 'none');
+ });
+ }
+ });
+
+ suite('label updates', () => {
+ let plugin;
+
+ setup(() => {
+ pluginApi.install(p => {
+ plugin = p;
+ plugin.registerStyleModule('change-metadata', 'my-plugin-style');
+ }, undefined, 'http://test.com/style.js');
+ sandbox.stub(pluginLoader, 'arePluginsLoaded').returns(true);
+ pluginLoader.loadPlugins([]);
+ element = createElement();
+ });
+
+ test('labels changed callback', done => {
+ let callCount = 0;
+ const labelChangeSpy = sandbox.spy(arg => {
+ callCount++;
+ if (callCount === 1) {
+ assert.deepEqual(arg, labels);
+ assert.equal(arg.CI.all.length, 2);
+ element.set(['change', 'labels'], {
+ CI: {
+ all: [
+ {value: 1, name: 'user 2', _account_id: 1},
+ ],
+ values: {
+ ' 0': 'Don\'t submit as-is',
+ '+1': 'No score',
+ '+2': 'Looks good to me',
+ },
+ },
+ });
+ } else if (callCount === 2) {
+ assert.equal(arg.CI.all.length, 1);
+ done();
+ }
+ });
+
+ plugin.changeMetadata().onLabelsChanged(labelChangeSpy);
+ });
+ });
+});
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js
similarity index 94%
rename from polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
rename to polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js
index 8e780d7..27f9b85 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js
@@ -1,44 +1,30 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2015 The Android Open Source Project
+/**
+ * @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.
+ */
-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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<meta charset="utf-8">
-<title>gr-change-metadata</title>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/components/wct-browser-legacy/browser.js"></script>
-
-<test-fixture id="basic">
- <template>
- <gr-change-metadata></gr-change-metadata>
- </template>
-</test-fixture>
-
-<script type="module">
-import '../../../test/common-test-setup.js';
+import '../../../test/common-test-setup-karma.js';
import '../../core/gr-router/gr-router.js';
import './gr-change-metadata.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
+import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
+const basicFixture = fixtureFromElement('gr-change-metadata');
+
const pluginApi = _testOnly_initGerritPluginApi();
suite('gr-change-metadata tests', () => {
@@ -47,15 +33,13 @@
setup(() => {
sandbox = sinon.sandbox.create();
- stub('gr-endpoint-decorator', {
- _import: sandbox.stub().returns(Promise.resolve()),
- });
stub('gr-rest-api-interface', {
getConfig() { return Promise.resolve({}); },
getLoggedIn() { return Promise.resolve(false); },
});
- element = fixture('basic');
+ element = basicFixture.instantiate();
+ sandbox.stub(pluginEndpoints, 'importUrl', url => Promise.resolve());
});
teardown(() => {
@@ -796,5 +780,4 @@
});
});
});
-});
-</script>
+});
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/test/plugin.html b/polygerrit-ui/app/elements/change/gr-change-metadata/test/plugin.html
deleted file mode 100644
index b3aa98f..0000000
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/test/plugin.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<dom-module id="my-plugin">
- <script>
- Gerrit.install(plugin => {
- plugin.registerStyleModule('change-metadata', 'my-plugin-style');
- });
- </script>
-</dom-module>
-
-<dom-module id="my-plugin-style">
- <template>
- <style>
- html {
- --change-metadata-assignee: {
- display: none;
- }
- --change-metadata-label-status: {
- display: none;
- }
- --change-metadata-strategy: {
- display: none;
- }
- --change-metadata-topic: {
- display: none;
- }
- }
- </style>
- </template>
-</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
index faa81b6..acd16d7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.js
@@ -279,9 +279,6 @@
setup(() => {
sandbox = sinon.sandbox.create();
- stub('gr-endpoint-decorator', {
- _import: sandbox.stub().returns(Promise.resolve()),
- });
// Since pluginEndpoints are global, must reset state.
_testOnly_resetEndpoints();
navigateToChangeStub = sandbox.stub(GerritNav, 'navigateToChange');
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
deleted file mode 100644
index d3232e9..0000000
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
+++ /dev/null
@@ -1,175 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2015 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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<meta charset="utf-8">
-<title>gr-reply-dialog</title>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/components/wct-browser-legacy/browser.js"></script>
-
-<test-fixture id="basic">
- <template>
- <gr-reply-dialog></gr-reply-dialog>
- </template>
-</test-fixture>
-
-<test-fixture id="plugin-host">
- <template>
- <gr-plugin-host></gr-plugin-host>
- </template>
-</test-fixture>
-
-<script type="module">
-import '../../../test/common-test-setup.js';
-import '../../plugins/gr-plugin-host/gr-plugin-host.js';
-import './gr-reply-dialog.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {resetPlugins} from '../../../test/test-utils.js';
-import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
-
-_testOnly_initGerritPluginApi();
-
-suite('gr-reply-dialog tests', () => {
- let element;
- let changeNum;
- let patchNum;
-
- let sandbox;
-
- const setupElement = element => {
- element.change = {
- _number: changeNum,
- labels: {
- 'Verified': {
- values: {
- '-1': 'Fails',
- ' 0': 'No score',
- '+1': 'Verified',
- },
- default_value: 0,
- },
- 'Code-Review': {
- values: {
- '-2': 'Do not submit',
- '-1': 'I would prefer that you didn\'t submit this',
- ' 0': 'No score',
- '+1': 'Looks good to me, but someone else must approve',
- '+2': 'Looks good to me, approved',
- },
- all: [{_account_id: 42, value: 0}],
- default_value: 0,
- },
- },
- };
- element.patchNum = patchNum;
- element.permittedLabels = {
- 'Code-Review': [
- '-1',
- ' 0',
- '+1',
- ],
- 'Verified': [
- '-1',
- ' 0',
- '+1',
- ],
- };
- sandbox.stub(element, 'fetchChangeUpdates')
- .returns(Promise.resolve({isLatest: true}));
- };
-
- setup(() => {
- sandbox = sinon.sandbox.create();
-
- changeNum = 42;
- patchNum = 1;
-
- stub('gr-rest-api-interface', {
- getConfig() { return Promise.resolve({}); },
- getAccount() { return Promise.resolve({_account_id: 42}); },
- });
-
- element = fixture('basic');
- setupElement(element);
-
- // Allow the elements created by dom-repeat to be stamped.
- flushAsynchronousOperations();
- });
-
- teardown(() => {
- sandbox.restore();
- });
-
- test('_submit blocked when invalid email is supplied to ccs', () => {
- const sendStub = sandbox.stub(element, 'send').returns(Promise.resolve());
- // Stub the below function to avoid side effects from the send promise
- // resolving.
- sandbox.stub(element, '_purgeReviewersPendingRemove');
-
- element.$.ccs.$.entry.setText('test');
- MockInteractions.tap(element.shadowRoot
- .querySelector('gr-button.send'));
- assert.isFalse(sendStub.called);
- flushAsynchronousOperations();
-
- element.$.ccs.$.entry.setText('test@test.test');
- MockInteractions.tap(element.shadowRoot
- .querySelector('gr-button.send'));
- assert.isTrue(sendStub.called);
- });
-
- test('lgtm plugin', done => {
- resetPlugins();
- const pluginHost = fixture('plugin-host');
- pluginHost.config = {
- plugin: {
- js_resource_paths: [],
- html_resource_paths: [
- new URL('test/plugin.html?' + Math.random(),
- window.location.href).toString(),
- ],
- },
- };
- element = fixture('basic');
- setupElement(element);
- const importSpy =
- sandbox.spy(element.shadowRoot
- .querySelector('gr-endpoint-decorator'), '_import');
- pluginLoader.awaitPluginsLoaded().then(() => {
- Promise.all(importSpy.returnValues).then(() => {
- flush(() => {
- const textarea = element.$.textarea.getNativeTextarea();
- textarea.value = 'LGTM';
- textarea.dispatchEvent(new CustomEvent(
- 'input', {bubbles: true, composed: true}));
- const labelScoreRows = dom(element.$.labelScores.root)
- .querySelector('gr-label-score-row[name="Code-Review"]');
- const selectedBtn = dom(labelScoreRows.root)
- .querySelector('gr-button[data-value="+1"].iron-selected');
- assert.isOk(selectedBtn);
- done();
- });
- });
- });
- });
-});
-</script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
new file mode 100644
index 0000000..2a8c418
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
@@ -0,0 +1,151 @@
+/**
+ * @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 '../../../test/common-test-setup-karma.js';
+import {resetPlugins} from '../../../test/test-utils.js';
+import './gr-reply-dialog.js';
+import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
+import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
+import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
+const basicFixture = fixtureFromElement('gr-reply-dialog');
+const pluginApi = _testOnly_initGerritPluginApi();
+
+suite('gr-reply-dialog tests', () => {
+ let element;
+ let changeNum;
+ let patchNum;
+
+ let sandbox;
+
+ const setupElement = element => {
+ element.change = {
+ _number: changeNum,
+ labels: {
+ 'Verified': {
+ values: {
+ '-1': 'Fails',
+ ' 0': 'No score',
+ '+1': 'Verified',
+ },
+ default_value: 0,
+ },
+ 'Code-Review': {
+ values: {
+ '-2': 'Do not submit',
+ '-1': 'I would prefer that you didn\'t submit this',
+ ' 0': 'No score',
+ '+1': 'Looks good to me, but someone else must approve',
+ '+2': 'Looks good to me, approved',
+ },
+ all: [{_account_id: 42, value: 0}],
+ default_value: 0,
+ },
+ },
+ };
+ element.patchNum = patchNum;
+ element.permittedLabels = {
+ 'Code-Review': [
+ '-1',
+ ' 0',
+ '+1',
+ ],
+ 'Verified': [
+ '-1',
+ ' 0',
+ '+1',
+ ],
+ };
+ sandbox.stub(element, 'fetchChangeUpdates')
+ .returns(Promise.resolve({isLatest: true}));
+ };
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+
+ changeNum = 42;
+ patchNum = 1;
+
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({}); },
+ getAccount() { return Promise.resolve({_account_id: 42}); },
+ });
+
+ element = basicFixture.instantiate();
+ setupElement(element);
+
+ // Allow the elements created by dom-repeat to be stamped.
+ flushAsynchronousOperations();
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ resetPlugins();
+ });
+
+ test('_submit blocked when invalid email is supplied to ccs', () => {
+ const sendStub = sandbox.stub(element, 'send').returns(Promise.resolve());
+ // Stub the below function to avoid side effects from the send promise
+ // resolving.
+ sandbox.stub(element, '_purgeReviewersPendingRemove');
+
+ element.$.ccs.$.entry.setText('test');
+ MockInteractions.tap(element.shadowRoot
+ .querySelector('gr-button.send'));
+ assert.isFalse(sendStub.called);
+ flushAsynchronousOperations();
+
+ element.$.ccs.$.entry.setText('test@test.test');
+ MockInteractions.tap(element.shadowRoot
+ .querySelector('gr-button.send'));
+ assert.isTrue(sendStub.called);
+ });
+
+ test('lgtm plugin', done => {
+ resetPlugins();
+ pluginApi.install(plugin => {
+ const replyApi = plugin.changeReply();
+ replyApi.addReplyTextChangedCallback(text => {
+ const label = 'Code-Review';
+ const labelValue = replyApi.getLabelValue(label);
+ if (labelValue &&
+ labelValue === ' 0' &&
+ text.indexOf('LGTM') === 0) {
+ replyApi.setLabelValue(label, '+1');
+ }
+ });
+ }, null, 'http://test.com/lgtm.js');
+ element = basicFixture.instantiate();
+ setupElement(element);
+ sandbox.stub(pluginEndpoints, 'importUrl', url => Promise.resolve());
+ pluginLoader.loadPlugins([]);
+ pluginLoader.awaitPluginsLoaded().then(() => {
+ flush(() => {
+ const textarea = element.$.textarea.getNativeTextarea();
+ textarea.value = 'LGTM';
+ textarea.dispatchEvent(new CustomEvent(
+ 'input', {bubbles: true, composed: true}));
+ const labelScoreRows = dom(element.$.labelScores.root)
+ .querySelector('gr-label-score-row[name="Code-Review"]');
+ const selectedBtn = dom(labelScoreRows.root)
+ .querySelector('gr-button[data-value="+1"].iron-selected');
+ assert.isOk(selectedBtn);
+ done();
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/test/plugin.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/test/plugin.html
deleted file mode 100644
index 94787e6..0000000
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/test/plugin.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<!--
-@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.
--->
-<dom-module id="my-plugin">
- <script>
- Gerrit.install(plugin => {
- const replyApi = plugin.changeReply();
- replyApi.addReplyTextChangedCallback(text => {
- const label = 'Code-Review';
- const labelValue = replyApi.getLabelValue(label);
- if (labelValue &&
- labelValue === ' 0' &&
- text.indexOf('LGTM') === 0) {
- replyApi.setLabelValue(label, '+1');
- }
- });
- });
- </script>
-</dom-module>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.js b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.js
index 69e2989..24438eb 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.js
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.js
@@ -102,12 +102,21 @@
suffixForDashboard: 'limit:10',
},
{
+ // Changes where the user is in the attention set.
+ name: 'Your Turn',
+ query: 'attention:${user}',
+ hideIfEmpty: false,
+ suffixForDashboard: 'limit:25',
+ attentionSetOnly: true,
+ },
+ {
// Changes that are assigned to the viewed user.
name: 'Assigned reviews',
query: 'assignee:${user} (-is:wip OR owner:self OR assignee:self) ' +
'is:open -is:ignored',
hideIfEmpty: true,
suffixForDashboard: 'limit:25',
+ assigneeOnly: true,
},
{
// WIP open changes owned by viewing user. This section is omitted when
@@ -730,8 +739,14 @@
},
getUserDashboard(user = 'self', sections = DEFAULT_SECTIONS,
- title = '') {
+ title = '', config = {}) {
+ const attentionEnabled =
+ config.change && !!config.change.enable_attention_set;
+ const assigneeEnabled =
+ config.change && !!config.change.enable_assignee;
sections = sections
+ .filter(section => (attentionEnabled || !section.attentionSetOnly))
+ .filter(section => (assigneeEnabled || !section.assigneeOnly))
.filter(section => (user === 'self' || !section.selfOnly))
.map(section => Object.assign({}, section, {
name: section.name,
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
index 4991770..c91819a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
-import {importHref} from '../../../scripts/import-href.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
@@ -65,15 +64,6 @@
pluginEndpoints.onDetachedEndpoint(this.name, this._endpointCallBack);
}
- /**
- * @suppress {checkTypes}
- */
- _import(url) {
- return new Promise((resolve, reject) => {
- importHref(url, resolve, reject);
- });
- }
-
_initDecoration(name, plugin, slot) {
const el = document.createElement(name);
return this._initProperties(el, plugin,
@@ -133,9 +123,9 @@
`plugin ${plugin.getPluginName()}, endpoint ${this.name}`);
}, INIT_PROPERTIES_TIMEOUT_MS));
return Promise.race([timeout, Promise.all(expectProperties)])
- .then(() => {
- clearTimeout(timeoutId);
- return el;
+ .then(() => el)
+ .finally(() => {
+ if (timeoutId) clearTimeout(timeoutId);
});
}
@@ -174,10 +164,7 @@
pluginEndpoints.onNewEndpoint(this.name, this._endpointCallBack);
if (this.name) {
pluginLoader.awaitPluginsLoaded()
- .then(() => Promise.all(
- pluginEndpoints.getPlugins(this.name).map(
- pluginUrl => this._import(pluginUrl)))
- )
+ .then(() => pluginEndpoints.getAndImportPlugins(this.name))
.then(() =>
pluginEndpoints
.getDetails(this.name)
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
similarity index 72%
rename from polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
rename to polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
index 890a457..17ada2c 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.js
@@ -1,61 +1,51 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2017 The Android Open Source Project
+/**
+ * @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.
+ */
-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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-endpoint-decorator</title>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/components/wct-browser-legacy/browser.js"></script>
-
-<test-fixture id="basic">
- <template>
- <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>
- </template>
-</test-fixture>
-
-<script type="module">
-import '../../../test/common-test-setup.js';
+import '../../../test/common-test-setup-karma.js';
import './gr-endpoint-decorator.js';
import '../gr-endpoint-param/gr-endpoint-param.js';
import '../gr-endpoint-slot/gr-endpoint-slot.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
+import {html} from '@polymer/polymer/lib/utils/html-tag.js';
import {resetPlugins} from '../../../test/test-utils.js';
import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
+import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
const pluginApi = _testOnly_initGerritPluginApi();
+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;
let sandbox;
@@ -66,11 +56,9 @@
setup(done => {
sandbox = sinon.sandbox.create();
- stub('gr-endpoint-decorator', {
- _import: sandbox.stub().returns(Promise.resolve()),
- });
resetPlugins();
- container = fixture('basic');
+ container = basicFixture.instantiate();
+ sandbox.stub(pluginEndpoints, 'importUrl', url => Promise.resolve());
pluginApi.install(p => plugin = p, '0.1',
'http://some/plugin/url.html');
// Decoration
@@ -90,16 +78,16 @@
teardown(() => {
sandbox.restore();
+ resetPlugins();
});
test('imports plugin-provided modules into endpoints', () => {
const endpoints =
Array.from(container.querySelectorAll('gr-endpoint-decorator'));
assert.equal(endpoints.length, 3);
- endpoints.forEach(element => {
- assert.isTrue(
- element._import.calledWith(new URL('http://some/plugin/url.html')));
- });
+ assert.isTrue(pluginEndpoints.importUrl.calledWith(
+ new URL('http://some/plugin/url.html')
+ ));
});
test('decoration', () => {
@@ -124,7 +112,7 @@
test('decoration with slot', () => {
const element =
container.querySelector('gr-endpoint-decorator[name="first"]');
- const modules = [...dom(element).querySelectorAll('p > some-module-2')];
+ const modules = [...dom(element).querySelectorAll('some-module-2')];
assert.equal(modules.length, 1);
const [module] = modules;
assert.isOk(module);
@@ -225,4 +213,3 @@
});
});
});
-</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
index f27053d..16176b3 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
@@ -15,7 +15,6 @@
* limitations under the License.
*/
import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
-import {importHref} from '../../../scripts/import-href.js';
import {updateStyles} from '@polymer/polymer/lib/mixins/element-mixin.js';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
@@ -35,10 +34,6 @@
static get properties() {
return {
name: String,
- _urlsImported: {
- type: Array,
- value() { return []; },
- },
_stylesApplied: {
type: Array,
value() { return []; },
@@ -46,23 +41,6 @@
};
}
- _importHref(url, resolve, reject) {
- // It is impossible to mock es6-module imported function.
- // The _importHref function is mocked in test.
- importHref(url, resolve, reject);
- }
-
- /**
- * @suppress {checkTypes}
- */
- _import(url) {
- if (this._urlsImported.includes(url)) { return Promise.resolve(); }
- this._urlsImported.push(url);
- return new Promise((resolve, reject) => {
- this._importHref(url, resolve, reject);
- });
- }
-
_applyStyle(name) {
if (this._stylesApplied.includes(name)) { return; }
this._stylesApplied.push(name);
@@ -79,14 +57,13 @@
}
_importAndApply() {
- Promise.all(pluginEndpoints.getPlugins(this.name).map(
- pluginUrl => this._import(pluginUrl))
- ).then(() => {
- const moduleNames = pluginEndpoints.getModules(this.name);
- for (const name of moduleNames) {
- this._applyStyle(name);
- }
- });
+ pluginEndpoints.getAndImportPlugins(this.name)
+ .then(() => {
+ const moduleNames = pluginEndpoints.getModules(this.name);
+ for (const name of moduleNames) {
+ this._applyStyle(name);
+ }
+ });
}
/** @override */
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
deleted file mode 100644
index 8f85348..0000000
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
+++ /dev/null
@@ -1,133 +0,0 @@
-<!DOCTYPE html>
-<!--
-@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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-external-style</title>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/components/wct-browser-legacy/browser.js"></script>
-<test-fixture id="basic">
- <template>
- <gr-external-style name="foo"></gr-external-style>
- </template>
-</test-fixture>
-
-<script type="module">
-import '../../../test/common-test-setup.js';
-import './gr-external-style.js';
-import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
-
-const pluginApi = _testOnly_initGerritPluginApi();
-
-suite('gr-external-style integration tests', () => {
- const TEST_URL = 'http://some/plugin/url.html';
-
- let sandbox;
- let element;
- let plugin;
- let importHrefStub;
-
- const installPlugin = () => {
- if (plugin) { return; }
- pluginApi.install(p => {
- plugin = p;
- }, '0.1', TEST_URL);
- };
-
- const createElement = () => {
- element = fixture('basic');
- sandbox.spy(element, '_applyStyle');
- };
-
- /**
- * Installs the plugin, creates the element, registers style module.
- */
- const lateRegister = () => {
- installPlugin();
- createElement();
- plugin.registerStyleModule('foo', 'some-module');
- };
-
- /**
- * Installs the plugin, registers style module, creates the element.
- */
- const earlyRegister = () => {
- installPlugin();
- plugin.registerStyleModule('foo', 'some-module');
- createElement();
- };
-
- setup(() => {
- sandbox = sinon.sandbox.create();
- importHrefStub = sandbox.stub().callsArg(1);
- stub('gr-external-style', {
- _importHref: (url, resolve, reject) => {
- importHrefStub(url, resolve, reject);
- },
- });
- sandbox.stub(pluginLoader, 'awaitPluginsLoaded')
- .returns(Promise.resolve());
- });
-
- teardown(() => {
- sandbox.restore();
- });
-
- test('imports plugin-provided module', async () => {
- lateRegister();
- await new Promise(flush);
- assert.isTrue(importHrefStub.calledWith(new URL(TEST_URL)));
- });
-
- test('applies plugin-provided styles', async () => {
- lateRegister();
- await new Promise(flush);
- assert.isTrue(element._applyStyle.calledWith('some-module'));
- });
-
- test('does not double import', async () => {
- earlyRegister();
- await new Promise(flush);
- plugin.registerStyleModule('foo', 'some-module');
- await new Promise(flush);
- const urlsImported =
- element._urlsImported.filter(url => url.toString() === TEST_URL);
- assert.strictEqual(urlsImported.length, 1);
- });
-
- test('does not double apply', async () => {
- earlyRegister();
- await new Promise(flush);
- plugin.registerStyleModule('foo', 'some-module');
- await new Promise(flush);
- const stylesApplied =
- element._stylesApplied.filter(name => name === 'some-module');
- assert.strictEqual(stylesApplied.length, 1);
- });
-
- test('loads and applies preloaded modules', async () => {
- earlyRegister();
- await new Promise(flush);
- assert.isTrue(importHrefStub.calledWith(new URL(TEST_URL)));
- assert.isTrue(element._applyStyle.calledWith('some-module'));
- });
-});
-</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.js b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.js
new file mode 100644
index 0000000..50e08cc
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.js
@@ -0,0 +1,116 @@
+/**
+ * @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 '../../../test/common-test-setup-karma.js';
+import {resetPlugins} from '../../../test/test-utils.js';
+import './gr-external-style.js';
+import {html} from '@polymer/polymer/lib/utils/html-tag.js';
+import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
+import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
+const pluginApi = _testOnly_initGerritPluginApi();
+
+const basicFixture = fixtureFromTemplate(
+ html`<gr-external-style name="foo"></gr-external-style>`
+);
+
+suite('gr-external-style integration tests', () => {
+ const TEST_URL = 'http://some/plugin/url.html';
+
+ let sandbox;
+ let element;
+ let plugin;
+
+ const installPlugin = () => {
+ if (plugin) { return; }
+ pluginApi.install(p => {
+ plugin = p;
+ }, '0.1', TEST_URL);
+ };
+
+ const createElement = () => {
+ element = basicFixture.instantiate();
+ sandbox.spy(element, '_applyStyle');
+ };
+
+ /**
+ * Installs the plugin, creates the element, registers style module.
+ */
+ const lateRegister = () => {
+ installPlugin();
+ createElement();
+ plugin.registerStyleModule('foo', 'some-module');
+ };
+
+ /**
+ * Installs the plugin, registers style module, creates the element.
+ */
+ const earlyRegister = () => {
+ installPlugin();
+ plugin.registerStyleModule('foo', 'some-module');
+ createElement();
+ };
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ sandbox.stub(pluginEndpoints, 'importUrl', url => Promise.resolve());
+ sandbox.stub(pluginLoader, 'awaitPluginsLoaded')
+ .returns(Promise.resolve());
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ resetPlugins();
+ });
+
+ test('imports plugin-provided module', async () => {
+ lateRegister();
+ await new Promise(flush);
+ assert.isTrue(pluginEndpoints.importUrl.calledWith(new URL(TEST_URL)));
+ });
+
+ test('applies plugin-provided styles', async () => {
+ lateRegister();
+ await new Promise(flush);
+ assert.isTrue(element._applyStyle.calledWith('some-module'));
+ });
+
+ test('does not double import', async () => {
+ earlyRegister();
+ await new Promise(flush);
+ plugin.registerStyleModule('foo', 'some-module');
+ await new Promise(flush);
+ // since loaded, should not call again
+ assert.isFalse(pluginEndpoints.importUrl.calledOnce);
+ });
+
+ test('does not double apply', async () => {
+ earlyRegister();
+ await new Promise(flush);
+ plugin.registerStyleModule('foo', 'some-module');
+ await new Promise(flush);
+ const stylesApplied =
+ element._stylesApplied.filter(name => name === 'some-module');
+ assert.strictEqual(stylesApplied.length, 1);
+ });
+
+ test('loads and applies preloaded modules', async () => {
+ earlyRegister();
+ await new Promise(flush);
+ assert.isTrue(element._applyStyle.calledWith('some-module'));
+ });
+});
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js
index 0727397..2c97df0 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js
@@ -16,151 +16,201 @@
*/
import {pluginLoader} from './gr-plugin-loader.js';
+import {importHref} from '../../../scripts/import-href.js';
/** @constructor */
-export function GrPluginEndpoints() {
- this._endpoints = {};
- this._callbacks = {};
- this._dynamicPlugins = {};
-}
-
-GrPluginEndpoints.prototype.onNewEndpoint = function(endpoint, callback) {
- if (!this._callbacks[endpoint]) {
- this._callbacks[endpoint] = [];
+export class GrPluginEndpoints {
+ constructor() {
+ this._endpoints = {};
+ this._callbacks = {};
+ this._dynamicPlugins = {};
+ this._importedUrls = new Set();
}
- this._callbacks[endpoint].push(callback);
-};
-GrPluginEndpoints.prototype.onDetachedEndpoint = function(endpoint,
- callback) {
- if (this._callbacks[endpoint]) {
- this._callbacks[endpoint] = this._callbacks[endpoint]
- .filter(cb => cb !== callback);
- }
-};
-
-GrPluginEndpoints.prototype._getOrCreateModuleInfo = function(plugin, opts) {
- const {endpoint, slot, type, moduleName, domHook} = opts;
- const existingModule = this._endpoints[endpoint].find(info =>
- info.plugin === plugin &&
- info.moduleName === moduleName &&
- info.domHook === domHook &&
- info.slot === slot
- );
- if (existingModule) {
- return existingModule;
- } else {
- const newModule = {
- moduleName,
- plugin,
- pluginUrl: plugin._url,
- type,
- domHook,
- slot,
- };
- this._endpoints[endpoint].push(newModule);
- return newModule;
- }
-};
-
-/**
- * Register a plugin to an endpoint.
- *
- * Dynamic plugins are registered to a specific prefix, such as
- * 'change-list-header'. These plugins are then fetched by prefix to determine
- * which endpoints to dynamically add to the page.
- *
- * @param {Object} plugin
- * @param {Object} opts
- */
-GrPluginEndpoints.prototype.registerModule = function(plugin, opts) {
- const {endpoint, dynamicEndpoint} = opts;
- if (dynamicEndpoint) {
- if (!this._dynamicPlugins[dynamicEndpoint]) {
- this._dynamicPlugins[dynamicEndpoint] = new Set();
+ onNewEndpoint(endpoint, callback) {
+ if (!this._callbacks[endpoint]) {
+ this._callbacks[endpoint] = [];
}
- this._dynamicPlugins[dynamicEndpoint].add(endpoint);
+ this._callbacks[endpoint].push(callback);
}
- if (!this._endpoints[endpoint]) {
- this._endpoints[endpoint] = [];
- }
- const moduleInfo = this._getOrCreateModuleInfo(plugin, opts);
- if (pluginLoader.arePluginsLoaded() && this._callbacks[endpoint]) {
- this._callbacks[endpoint].forEach(callback => callback(moduleInfo));
- }
-};
-GrPluginEndpoints.prototype.getDynamicEndpoints = function(dynamicEndpoint) {
- const plugins = this._dynamicPlugins[dynamicEndpoint];
- if (!plugins) return [];
- return Array.from(plugins);
-};
-
-/**
- * Get detailed information about modules registered with an extension
- * endpoint.
- *
- * @param {string} name Endpoint name.
- * @param {?{
- * type: (string|undefined),
- * moduleName: (string|undefined)
- * }} opt_options
- * @return {!Array<{
- * moduleName: string,
- * plugin: Plugin,
- * pluginUrl: String,
- * type: EndpointType,
- * domHook: !Object
- * }>}
- */
-GrPluginEndpoints.prototype.getDetails = function(name, opt_options) {
- const type = opt_options && opt_options.type;
- const moduleName = opt_options && opt_options.moduleName;
- if (!this._endpoints[name]) {
- return [];
+ onDetachedEndpoint(endpoint, callback) {
+ if (this._callbacks[endpoint]) {
+ this._callbacks[endpoint] = this._callbacks[endpoint].filter(
+ cb => cb !== callback
+ );
+ }
}
- return this._endpoints[name]
- .filter(item => (!type || item.type === type) &&
- (!moduleName || moduleName == item.moduleName));
-};
-/**
- * Get detailed module names for instantiating at the endpoint.
- *
- * @param {string} name Endpoint name.
- * @param {?{
- * type: (string|undefined),
- * moduleName: (string|undefined)
- * }} opt_options
- * @return {!Array<string>}
- */
-GrPluginEndpoints.prototype.getModules = function(name, opt_options) {
- const modulesData = this.getDetails(name, opt_options);
- if (!modulesData.length) {
- return [];
+ _getOrCreateModuleInfo(plugin, opts) {
+ const {endpoint, slot, type, moduleName, domHook} = opts;
+ const existingModule = this._endpoints[endpoint].find(
+ info =>
+ info.plugin === plugin &&
+ info.moduleName === moduleName &&
+ info.domHook === domHook &&
+ info.slot === slot
+ );
+ if (existingModule) {
+ return existingModule;
+ } else {
+ const newModule = {
+ moduleName,
+ plugin,
+ pluginUrl: plugin._url,
+ type,
+ domHook,
+ slot,
+ };
+ this._endpoints[endpoint].push(newModule);
+ return newModule;
+ }
}
- return modulesData.map(m => m.moduleName);
-};
-/**
- * Get .html plugin URLs with element and module definitions.
- *
- * @param {string} name Endpoint name.
- * @param {?{
- * type: (string|undefined),
- * moduleName: (string|undefined)
- * }} opt_options
- * @return {!Array<!URL>}
- */
-GrPluginEndpoints.prototype.getPlugins = function(name, opt_options) {
- const modulesData =
- this.getDetails(name, opt_options).filter(
- data => data.pluginUrl.pathname.includes('.html'));
- if (!modulesData.length) {
- return [];
+ /**
+ * Register a plugin to an endpoint.
+ *
+ * Dynamic plugins are registered to a specific prefix, such as
+ * 'change-list-header'. These plugins are then fetched by prefix to determine
+ * which endpoints to dynamically add to the page.
+ *
+ * @param {Object} plugin
+ * @param {Object} opts
+ */
+ registerModule(plugin, opts) {
+ const {endpoint, dynamicEndpoint} = opts;
+ if (dynamicEndpoint) {
+ if (!this._dynamicPlugins[dynamicEndpoint]) {
+ this._dynamicPlugins[dynamicEndpoint] = new Set();
+ }
+ this._dynamicPlugins[dynamicEndpoint].add(endpoint);
+ }
+ if (!this._endpoints[endpoint]) {
+ this._endpoints[endpoint] = [];
+ }
+ const moduleInfo = this._getOrCreateModuleInfo(plugin, opts);
+ if (pluginLoader.arePluginsLoaded() && this._callbacks[endpoint]) {
+ this._callbacks[endpoint].forEach(callback => callback(moduleInfo));
+ }
}
- return Array.from(new Set(modulesData.map(m => m.pluginUrl)));
-};
+
+ getDynamicEndpoints(dynamicEndpoint) {
+ const plugins = this._dynamicPlugins[dynamicEndpoint];
+ if (!plugins) return [];
+ return Array.from(plugins);
+ }
+
+ /**
+ * Get detailed information about modules registered with an extension
+ * endpoint.
+ *
+ * @param {string} name Endpoint name.
+ * @param {?{
+ * type: (string|undefined),
+ * moduleName: (string|undefined)
+ * }} opt_options
+ * @return {!Array<{
+ * moduleName: string,
+ * plugin: Plugin,
+ * pluginUrl: String,
+ * type: EndpointType,
+ * domHook: !Object
+ * }>}
+ */
+ getDetails(name, opt_options) {
+ const type = opt_options && opt_options.type;
+ const moduleName = opt_options && opt_options.moduleName;
+ if (!this._endpoints[name]) {
+ return [];
+ }
+ return this._endpoints[name].filter(
+ item =>
+ (!type || item.type === type) &&
+ (!moduleName || moduleName == item.moduleName)
+ );
+ }
+
+ /**
+ * Get detailed module names for instantiating at the endpoint.
+ *
+ * @param {string} name Endpoint name.
+ * @param {?{
+ * type: (string|undefined),
+ * moduleName: (string|undefined)
+ * }} opt_options
+ * @return {!Array<string>}
+ */
+ getModules(name, opt_options) {
+ const modulesData = this.getDetails(name, opt_options);
+ if (!modulesData.length) {
+ return [];
+ }
+ return modulesData.map(m => m.moduleName);
+ }
+
+ /**
+ * Get plugin URLs with element and module definitions.
+ *
+ * @param {string} name Endpoint name.
+ * @param {?{
+ * type: (string|undefined),
+ * moduleName: (string|undefined)
+ * }} opt_options
+ * @return {!Array<!URL>}
+ */
+ getPlugins(name, opt_options) {
+ const modulesData = this.getDetails(name, opt_options);
+ if (!modulesData.length) {
+ return [];
+ }
+ return Array.from(new Set(modulesData.map(m => m.pluginUrl)));
+ }
+
+ importUrl(pluginUrl) {
+ let timerId;
+ return Promise
+ .race([
+ new Promise((resolve, reject) => {
+ this._importedUrls.add(pluginUrl.href);
+ importHref(pluginUrl, resolve, reject);
+ }),
+ // Timeout after 3s
+ new Promise(r => timerId = setTimeout(r, 3000)),
+ ])
+ .finally(() => {
+ if (timerId) clearTimeout(timerId);
+ });
+ }
+
+ /**
+ * Get plugin URLs with element and module definitions.
+ *
+ * @param {string} name Endpoint name.
+ * @param {?{
+ * type: (string|undefined),
+ * moduleName: (string|undefined)
+ * }} opt_options
+ * @return {!Array<!Promise<void>>}
+ */
+ getAndImportPlugins(name, opt_options) {
+ return Promise.all(
+ this.getPlugins(name, opt_options).map(pluginUrl => {
+ if (this._importedUrls.has(pluginUrl.href)) {
+ return Promise.resolve();
+ }
+
+ // TODO: we will deprecate html plugins entirely
+ // for now, keep the original behavior and import
+ // only for html ones
+ if (pluginUrl && pluginUrl.pathname.endsWith('.html')) {
+ return this.importUrl(pluginUrl);
+ } else {
+ return Promise.resolve();
+ }
+ })
+ );
+ }
+}
// TODO(dmfilippov): Convert to service and add to appContext
export let pluginEndpoints = new GrPluginEndpoints();
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.js
similarity index 76%
rename from polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
rename to polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.js
index 3494e99..e6767bc 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.js
@@ -1,32 +1,22 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2017 The Android Open Source Project
+/**
+ * @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.
+ */
-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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<meta charset="utf-8">
-<title>gr-plugin-endpoints</title>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/components/wct-browser-legacy/browser.js"></script>
-
-<script type="module">
-import '../../../test/common-test-setup.js';
+import '../../../test/common-test-setup-karma.js';
+import {resetPlugins} from '../../../test/test-utils.js';
import './gr-js-api-interface.js';
import {GrPluginEndpoints} from './gr-plugin-endpoints.js';
import {pluginLoader} from './gr-plugin-loader.js';
@@ -68,10 +58,12 @@
}
);
sandbox.stub(pluginLoader, 'arePluginsLoaded').returns(true);
+ sandbox.spy(instance, 'importUrl');
});
teardown(() => {
sandbox.restore();
+ resetPlugins();
});
test('getDetails all', () => {
@@ -133,6 +125,14 @@
instance.getPlugins('a-place'), [pluginFoo._url]);
});
+ test('getAndImportPlugins', () => {
+ instance.getAndImportPlugins('a-place');
+ assert.isTrue(instance.importUrl.called);
+ assert.isTrue(instance.importUrl.calledOnce);
+ instance.getAndImportPlugins('a-place');
+ assert.isTrue(instance.importUrl.calledOnce);
+ });
+
test('onNewEndpoint', () => {
const newModuleStub = sandbox.stub();
instance.onNewEndpoint('a-place', newModuleStub);
@@ -176,5 +176,4 @@
},
]);
});
-});
-</script>
+});
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
index 6c5546e..8e2455e 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
@@ -416,7 +416,7 @@
() => {
reject(new Error(this._timeout()));
}, PLUGIN_LOADING_TIMEOUT_MS)),
- ]).then(() => {
+ ]).finally(() => {
if (timerId) clearTimeout(timerId);
});
}
diff --git a/polygerrit-ui/app/test/tests.js b/polygerrit-ui/app/test/tests.js
index 2d795ec..df6a667 100644
--- a/polygerrit-ui/app/test/tests.js
+++ b/polygerrit-ui/app/test/tests.js
@@ -57,8 +57,6 @@
'change-list/gr-repo-header/gr-repo-header_test.html',
'change-list/gr-user-header/gr-user-header_test.html',
'change/gr-change-actions/gr-change-actions_test.html',
- 'change/gr-change-metadata/gr-change-metadata-it_test.html',
- 'change/gr-change-metadata/gr-change-metadata_test.html',
'change/gr-change-requirements/gr-change-requirements_test.html',
'change/gr-comment-list/gr-comment-list_test.html',
'change/gr-commit-info/gr-commit-info_test.html',
@@ -79,7 +77,6 @@
'change/gr-messages-list/gr-messages-list_test.html',
'change/gr-messages-list/gr-messages-list-experimental_test.html',
'change/gr-related-changes-list/gr-related-changes-list_test.html',
- 'change/gr-reply-dialog/gr-reply-dialog-it_test.html',
'change/gr-reply-dialog/gr-reply-dialog_test.html',
'change/gr-reviewer-list/gr-reviewer-list_test.html',
'change/gr-thread-list/gr-thread-list_test.html',
@@ -122,9 +119,7 @@
'plugins/gr-styles-api/gr-styles-api_test.html',
'plugins/gr-attribute-helper/gr-attribute-helper_test.html',
'plugins/gr-dom-hooks/gr-dom-hooks_test.html',
- 'plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html',
'plugins/gr-event-helper/gr-event-helper_test.html',
- 'plugins/gr-external-style/gr-external-style_test.html',
'plugins/gr-plugin-host/gr-plugin-host_test.html',
'plugins/gr-popup-interface/gr-plugin-popup_test.html',
'plugins/gr-popup-interface/gr-popup-interface_test.html',
@@ -180,7 +175,6 @@
'shared/gr-js-api-interface/gr-gerrit_test.html',
'shared/gr-js-api-interface/gr-plugin-action-context_test.html',
'shared/gr-js-api-interface/gr-plugin-loader_test.html',
- 'shared/gr-js-api-interface/gr-plugin-endpoints_test.html',
'shared/gr-js-api-interface/gr-plugin-rest-api_test.html',
'shared/gr-fixed-panel/gr-fixed-panel_test.html',
'shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html',