Merge changes I711e72e8,Ie23c829e,I04c40d11
* changes:
Fix JdkObsolete issues with SortedMap
Fix JdkObsolete issues with SortedSet
Fix/ignore JdkObsolete issues with StringBuffer
diff --git a/java/com/google/gerrit/index/query/IndexPredicate.java b/java/com/google/gerrit/index/query/IndexPredicate.java
index 18d7fbc..b65fb96 100644
--- a/java/com/google/gerrit/index/query/IndexPredicate.java
+++ b/java/com/google/gerrit/index/query/IndexPredicate.java
@@ -35,7 +35,7 @@
* complexity was reduced to the bare minimum at the cost of small discrepancies to the Unicode
* spec.
*/
- private static final Splitter FULL_TEXT_SPLITTER = Splitter.on(CharMatcher.anyOf(" ,.-:\\/_\n"));
+ private static final Splitter FULL_TEXT_SPLITTER = Splitter.on(CharMatcher.anyOf(" ,.-:\\/_=\n"));
private final FieldDef<I, ?> def;
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java b/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
index 63c5297..d57f800 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
@@ -39,6 +39,7 @@
import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gerrit.server.query.change.IsSubmittablePredicate;
import com.google.gerrit.server.query.change.OrSource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -182,6 +183,7 @@
private Predicate<ChangeData> rewriteImpl(
Predicate<ChangeData> in, ChangeIndex index, QueryOptions opts, MutableInteger leafTerms)
throws QueryParseException {
+ in = IsSubmittablePredicate.rewrite(in);
if (isIndexPredicate(in, index)) {
if (++leafTerms.value > config.maxTerms()) {
throw new TooManyTermsInQueryException();
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 4cecb3f..80b3322 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -730,7 +730,7 @@
Predicate.not(new SubmittablePredicate(SubmitRecord.Status.RULE_ERROR)));
}
checkFieldAvailable(ChangeField.IS_SUBMITTABLE, "is:submittable");
- return new BooleanPredicate(ChangeField.IS_SUBMITTABLE);
+ return new IsSubmittablePredicate();
}
if ("ignored".equalsIgnoreCase(value)) {
diff --git a/java/com/google/gerrit/server/query/change/IsSubmittablePredicate.java b/java/com/google/gerrit/server/query/change/IsSubmittablePredicate.java
new file mode 100644
index 0000000..17de132
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/IsSubmittablePredicate.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 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.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.index.query.NotPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.server.index.change.ChangeField;
+
+public class IsSubmittablePredicate extends BooleanPredicate {
+ public IsSubmittablePredicate() {
+ super(ChangeField.IS_SUBMITTABLE);
+ }
+
+ /**
+ * Rewrite the is:submittable predicate.
+ *
+ * <p>If we run a query with "is:submittable OR -is:submittable" the result should match all
+ * changes. In Lucene, we keep separate sub-indexes for open and closed changes. The Lucene
+ * backend inspects the input predicate and depending on all its child predicates decides if the
+ * query should run against the open sub-index, closed sub-index or both.
+ *
+ * <p>The "is:submittable" operator is implemented as:
+ *
+ * <p>issubmittable:1
+ *
+ * <p>But we want to exclude closed changes from being matched by this query. For the normal case,
+ * we rewrite the query as:
+ *
+ * <p>issubmittable:1 AND status:new
+ *
+ * <p>Hence Lucene will match the query against the open sub-index. For the negated case (i.e.
+ * "-is:submittable"), we cannot just negate the previous query because it would result in:
+ *
+ * <p>-(issubmittable:1 AND status:new)
+ *
+ * <p>Lucene will conclude that it should look for changes that are <b>not</b> new and hence will
+ * run the query against the closed sub-index, not matching with changes that are open but not
+ * submittable. For this case, we need to rewrite the query to match with closed changes <b>or</b>
+ * changes that are not submittable.
+ */
+ public static Predicate<ChangeData> rewrite(Predicate<ChangeData> in) {
+ if (in instanceof IsSubmittablePredicate) {
+ return Predicate.and(
+ new BooleanPredicate(ChangeField.IS_SUBMITTABLE), ChangeStatusPredicate.open());
+ }
+ if (in instanceof NotPredicate && in.getChild(0) instanceof IsSubmittablePredicate) {
+ return Predicate.or(
+ Predicate.not(new BooleanPredicate(ChangeField.IS_SUBMITTABLE)),
+ ChangeStatusPredicate.closed());
+ }
+ return in;
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index ba86976..d911512 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
@@ -46,16 +47,21 @@
@Test
public void newPatchSetsNotifyConfig() throws Exception {
- Address addr = Address.create("Watcher", "watcher@example.com");
- NotifyConfig.Builder nc = NotifyConfig.builder();
- nc.addAddress(addr);
- nc.setName("new-patch-set");
- nc.setHeader(NotifyConfig.Header.CC);
- nc.setNotify(EnumSet.of(NotifyType.NEW_PATCHSETS));
- nc.setFilter("message:sekret");
-
+ ImmutableList<String> messageFilters =
+ ImmutableList.of("message:subject-with-tokens", "message:subject-with-tokens=secret");
+ ImmutableList.Builder<Address> watchers = ImmutableList.builder();
try (ProjectConfigUpdate u = updateProject(project)) {
- u.getConfig().putNotifyConfig("watch", nc.build());
+ for (int i = 0; i < messageFilters.size(); i++) {
+ Address addr = Address.create("Watcher#" + i, String.format("watcher-%s@example.com", i));
+ watchers.add(addr);
+ NotifyConfig.Builder nc = NotifyConfig.builder();
+ nc.addAddress(addr);
+ nc.setName("new-patch-set" + i);
+ nc.setHeader(NotifyConfig.Header.CC);
+ nc.setNotify(EnumSet.of(NotifyType.NEW_PATCHSETS));
+ nc.setFilter(messageFilters.get(i));
+ u.getConfig().putNotifyConfig("watch" + i, nc.build());
+ }
u.save();
}
@@ -67,7 +73,13 @@
r =
pushFactory
- .create(admin.newIdent(), testRepo, "super sekret subject", "a", "a2", r.getChangeId())
+ .create(
+ admin.newIdent(),
+ testRepo,
+ "super sekret subject\n\nsubject-with-tokens=secret subject",
+ "a",
+ "a2",
+ r.getChangeId())
.to("refs/for/master");
r.assertOkStatus();
@@ -80,7 +92,7 @@
List<Message> messages = sender.getMessages();
assertThat(messages).hasSize(1);
Message m = messages.get(0);
- assertThat(m.rcpt()).containsExactly(addr);
+ assertThat(m.rcpt()).containsExactlyElementsIn(watchers.build());
assertThat(m.body()).contains("Change subject: super sekret subject\n");
assertThat(m.body()).contains("Gerrit-PatchSet: 2\n");
}
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 3990c1f..4853376 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -3041,6 +3041,10 @@
// NEED records don't have associated users.
assertQuery("label:CodE-RevieW=need,user1");
assertQuery("label:CodE-RevieW=need,user");
+
+ gApi.changes().id(change1.getId().get()).current().submit();
+ assertQuery("is:submittable");
+ assertQuery("-is:submittable", change1, change2);
}
@Test
diff --git a/lib/nongoogle_test.sh b/lib/nongoogle_test.sh
index 90d38b0..c008982 100755
--- a/lib/nongoogle_test.sh
+++ b/lib/nongoogle_test.sh
@@ -19,6 +19,7 @@
dropwizard-core
duct-tape
eddsa
+error-prone-annotations
flogger
flogger-log4j-backend
flogger-system-backend
diff --git a/plugins/replication b/plugins/replication
index 5a2cb73..36ed18a 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 5a2cb73d8131d1f20e08e815803f46266ca9f74f
+Subproject commit 36ed18af69d005a7cf89a9bba2f2585ead8d46da
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 23a6a2a..898de14 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -97,8 +97,6 @@
"elements/admin/gr-permission/gr-permission_html.ts",
"elements/admin/gr-repo-access/gr-repo-access_html.ts",
"elements/admin/gr-rule-editor/gr-rule-editor_html.ts",
- "elements/change-list/gr-change-list-view/gr-change-list-view_html.ts",
- "elements/change-list/gr-change-list/gr-change-list_html.ts",
"elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts",
"elements/change/gr-change-actions/gr-change-actions_html.ts",
"elements/change/gr-change-metadata/gr-change-metadata_html.ts",
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index b68892f..142abaa 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -19,12 +19,8 @@
import '../gr-change-list/gr-change-list';
import '../gr-repo-header/gr-repo-header';
import '../gr-user-header/gr-user-header';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-change-list-view_html';
import {page} from '../../../utils/page-wrapper-utils';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {customElement, property} from '@polymer/decorators';
import {AppElementParams} from '../../gr-app-types';
import {
AccountDetailInfo,
@@ -36,10 +32,14 @@
} from '../../../types/common';
import {ChangeStarToggleStarDetail} from '../../shared/gr-change-star/gr-change-star';
import {ChangeListViewState} from '../../../types/types';
-import {fireTitleChange} from '../../../utils/event-util';
+import {fire, fireTitleChange} from '../../../utils/event-util';
import {getAppContext} from '../../../services/app-context';
import {GerritView} from '../../../services/router/router-model';
import {RELOAD_DASHBOARD_INTERVAL_MS} from '../../../constants/constants';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, PropertyValues, html, css} from 'lit';
+import {customElement, property, state, query} from 'lit/decorators';
+import {ValueChangedEvent} from '../../../types/events';
const LOOKUP_QUERY_PATTERNS: RegExp[] = [
/^\s*i?[0-9a-f]{7,40}\s*$/i, // CHANGE_ID
@@ -54,60 +54,50 @@
const LIMIT_OPERATOR_PATTERN = /\blimit:(\d+)/i;
-export interface GrChangeListView {
- $: {
- prevArrow: HTMLAnchorElement;
- nextArrow: HTMLAnchorElement;
- };
-}
-
@customElement('gr-change-list-view')
-export class GrChangeListView extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrChangeListView extends LitElement {
/**
* Fired when the title of the page should change.
*
* @event title-change
*/
- @property({type: Object, observer: '_paramsChanged'})
- params?: AppElementParams;
+ @query('#prevArrow') protected prevArrow?: HTMLAnchorElement;
- @property({type: Boolean, computed: '_computeLoggedIn(account)'})
- _loggedIn?: boolean;
+ @query('#nextArrow') protected nextArrow?: HTMLAnchorElement;
+
+ @property({type: Object})
+ params?: AppElementParams;
@property({type: Object})
account: AccountDetailInfo | null = null;
- @property({type: Object, notify: true})
+ @property({type: Object})
viewState: ChangeListViewState = {};
@property({type: Object})
preferences?: PreferencesInput;
- @property({type: Number})
- _changesPerPage?: number;
+ // private but used in test
+ @state() changesPerPage?: number;
- @property({type: String})
- _query = '';
+ // private but used in test
+ @state() query = '';
- @property({type: Number})
- _offset?: number;
+ // private but used in test
+ @state() offset?: number;
- @property({type: Array, observer: '_changesChanged'})
- _changes?: ChangeInfo[];
+ // private but used in test
+ @state() changes?: ChangeInfo[];
- @property({type: Boolean})
- _loading = true;
+ // private but used in test
+ @state() loading = true;
- @property({type: String})
- _userId: AccountId | EmailAddress | null = null;
+ // private but used in test
+ @state() userId: AccountId | EmailAddress | null = null;
- @property({type: String})
- _repo: RepoName | null = null;
+ // private but used in test
+ @state() repo: RepoName | null = null;
private readonly restApiService = getAppContext().restApiService;
@@ -117,8 +107,8 @@
constructor() {
super();
- this.addEventListener('next-page', () => this._handleNextPage());
- this.addEventListener('previous-page', () => this._handlePreviousPage());
+ this.addEventListener('next-page', () => this.handleNextPage());
+ this.addEventListener('previous-page', () => this.handlePreviousPage());
this.addEventListener('reload', () => this.reload());
// We are not currently verifying if the view is actually visible. We rely
// on gr-app-element to restamp the component if view changes
@@ -137,37 +127,176 @@
override connectedCallback() {
super.connectedCallback();
- this._loadPreferences();
+ this.loadPreferences();
+ }
+
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ display: block;
+ }
+ .loading {
+ color: var(--deemphasized-text-color);
+ padding: var(--spacing-l);
+ }
+ gr-change-list {
+ width: 100%;
+ }
+ gr-user-header,
+ gr-repo-header {
+ border-bottom: 1px solid var(--border-color);
+ }
+ nav {
+ align-items: center;
+ display: flex;
+ height: 3rem;
+ justify-content: flex-end;
+ margin-right: 20px;
+ }
+ nav,
+ iron-icon {
+ color: var(--deemphasized-text-color);
+ }
+ iron-icon {
+ height: 1.85rem;
+ margin-left: 16px;
+ width: 1.85rem;
+ }
+ .hide {
+ display: none;
+ }
+ @media only screen and (max-width: 50em) {
+ .loading,
+ .error {
+ padding: 0 var(--spacing-l);
+ }
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ if (this.loading) return html`<div class="loading">Loading...</div>`;
+ const loggedIn = !!(this.account && Object.keys(this.account).length > 0);
+ return html`
+ <div>
+ ${this.renderRepoHeader()} ${this.renderUserHeader(loggedIn)}
+ <gr-change-list
+ .account=${this.account}
+ .changes=${this.changes}
+ .preferences=${this.preferences}
+ .selectedIndex=${this.viewState.selectedChangeIndex}
+ .showStar=${loggedIn}
+ @selected-index-changed=${(e: ValueChangedEvent<number>) => {
+ this.handleSelectedIndexChanged(e);
+ }}
+ @toggle-star=${(e: CustomEvent<ChangeStarToggleStarDetail>) => {
+ this.handleToggleStar(e);
+ }}
+ ></gr-change-list>
+ ${this.renderChangeListViewNav()}
+ </div>
+ `;
+ }
+
+ private renderRepoHeader() {
+ if (!this.repo) return;
+
+ return html` <gr-repo-header .repo=${this.repo}></gr-repo-header> `;
+ }
+
+ private renderUserHeader(loggedIn: boolean) {
+ if (!this.userId) return;
+
+ return html`
+ <gr-user-header
+ .userId=${this.userId}
+ showDashboardLink
+ .loggedIn=${loggedIn}
+ ></gr-user-header>
+ `;
+ }
+
+ private renderChangeListViewNav() {
+ if (this.loading || !this.changes || !this.changes.length) return;
+
+ return html`
+ <nav>
+ Page ${this.computePage()} ${this.renderPrevArrow()}
+ ${this.renderNextArrow()}
+ </nav>
+ `;
+ }
+
+ private renderPrevArrow() {
+ if (this.offset === 0) return;
+
+ return html`
+ <a id="prevArrow" href="${this.computeNavLink(-1)}">
+ <iron-icon icon="gr-icons:chevron-left" aria-label="Older"> </iron-icon>
+ </a>
+ `;
+ }
+
+ private renderNextArrow() {
+ if (
+ !(
+ this.changes?.length &&
+ this.changes[this.changes.length - 1]._more_changes
+ )
+ )
+ return;
+
+ return html`
+ <a id="nextArrow" href="${this.computeNavLink(1)}">
+ <iron-icon icon="gr-icons:chevron-right" aria-label="Newer">
+ </iron-icon>
+ </a>
+ `;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('params')) {
+ this.paramsChanged();
+ }
+
+ if (changedProperties.has('changes')) {
+ this.changesChanged();
+ }
}
reload() {
- if (this._loading) return;
- this._loading = true;
- this._getChanges().then(changes => {
- this._changes = changes || [];
- this._loading = false;
+ if (this.loading) return;
+ this.loading = true;
+ this.getChanges().then(changes => {
+ this.changes = changes || [];
+ this.loading = false;
});
}
- _paramsChanged(value: AppElementParams) {
- if (value.view !== GerritView.SEARCH) return;
+ private paramsChanged() {
+ const value = this.params;
+ if (!value || value.view !== GerritView.SEARCH) return;
- this._loading = true;
- this._query = value.query;
+ this.loading = true;
+ this.query = value.query;
const offset = Number(value.offset);
- this._offset = isNaN(offset) ? 0 : offset;
+ this.offset = isNaN(offset) ? 0 : offset;
if (
- this.viewState.query !== this._query ||
- this.viewState.offset !== this._offset
+ this.viewState.query !== this.query ||
+ this.viewState.offset !== this.offset
) {
- this.set('viewState.selectedChangeIndex', 0);
- this.set('viewState.query', this._query);
- this.set('viewState.offset', this._offset);
+ this.viewState.selectedChangeIndex = 0;
+ this.viewState.query = this.query;
+ this.viewState.offset = this.offset;
+ fire(this, 'view-state-changed', {value: this.viewState});
}
// NOTE: This method may be called before attachment. Fire title-change
// in an async so that attachment to the DOM can take place first.
- setTimeout(() => fireTitleChange(this, this._query));
+ setTimeout(() => fireTitleChange(this, this.query));
this.restApiService
.getPreferences()
@@ -175,14 +304,14 @@
if (!prefs) {
throw new Error('getPreferences returned undefined');
}
- this._changesPerPage = prefs.changes_per_page;
- return this._getChanges();
+ this.changesPerPage = prefs.changes_per_page;
+ return this.getChanges();
})
.then(changes => {
changes = changes || [];
- if (this._query && changes.length === 1) {
+ if (this.query && changes.length === 1) {
for (const queryPattern of LOOKUP_QUERY_PATTERNS) {
- if (this._query.match(queryPattern)) {
+ if (this.query.match(queryPattern)) {
// "Back"/"Forward" buttons work correctly only with
// opt_redirect options
GerritNav.navigateToChange(changes[0], {
@@ -192,12 +321,12 @@
}
}
}
- this._changes = changes;
- this._loading = false;
+ this.changes = changes;
+ this.loading = false;
});
}
- _loadPreferences() {
+ private loadPreferences() {
return this.restApiService.getLoggedIn().then(loggedIn => {
if (loggedIn) {
this.restApiService.getPreferences().then(preferences => {
@@ -209,15 +338,18 @@
});
}
- _getChanges() {
+ // private but used in test
+ getChanges() {
return this.restApiService.getChanges(
- this._changesPerPage,
- this._query,
- this._offset
+ this.changesPerPage,
+ this.query,
+ this.offset
);
}
- _limitFor(query: string, defaultLimit: number) {
+ // private but used in test
+ limitFor(query: string, defaultLimit?: number) {
+ if (defaultLimit === undefined) return 0;
const match = query.match(LIMIT_OPERATOR_PATTERN);
if (!match) {
return defaultLimit;
@@ -225,78 +357,53 @@
return Number(match[1]);
}
- _computeNavLink(
- query: string,
- offset: number | undefined,
- direction: number,
- changesPerPage: number
- ) {
- offset = offset ?? 0;
- const limit = this._limitFor(query, changesPerPage);
+ // private but used in test
+ computeNavLink(direction: number) {
+ const offset = this.offset ?? 0;
+ const limit = this.limitFor(this.query, this.changesPerPage);
const newOffset = Math.max(0, offset + limit * direction);
- return GerritNav.getUrlForSearchQuery(query, newOffset);
+ return GerritNav.getUrlForSearchQuery(this.query, newOffset);
}
- _computePrevArrowClass(offset?: number) {
- return offset === 0 ? 'hide' : '';
+ // private but used in test
+ handleNextPage() {
+ if (!this.nextArrow || !this.changesPerPage) return;
+ page.show(this.computeNavLink(1));
}
- _computeNextArrowClass(changes?: ChangeInfo[]) {
- const more = changes?.length && changes[changes.length - 1]._more_changes;
- return more ? '' : 'hide';
+ // private but used in test
+ handlePreviousPage() {
+ if (!this.prevArrow || !this.changesPerPage) return;
+ page.show(this.computeNavLink(-1));
}
- _computeNavClass(loading?: boolean) {
- return loading || !this._changes || !this._changes.length ? 'hide' : '';
- }
-
- _handleNextPage() {
- if (this.$.nextArrow.hidden || !this._changesPerPage) return;
- page.show(
- this._computeNavLink(this._query, this._offset, 1, this._changesPerPage)
- );
- }
-
- _handlePreviousPage() {
- if (this.$.prevArrow.hidden || !this._changesPerPage) return;
- page.show(
- this._computeNavLink(this._query, this._offset, -1, this._changesPerPage)
- );
- }
-
- _changesChanged(changes?: ChangeInfo[]) {
- this._userId = null;
- this._repo = null;
+ private changesChanged() {
+ this.userId = null;
+ this.repo = null;
+ const changes = this.changes;
if (!changes || !changes.length) {
return;
}
- if (USER_QUERY_PATTERN.test(this._query)) {
+ if (USER_QUERY_PATTERN.test(this.query)) {
const owner = changes[0].owner;
const userId = owner._account_id ? owner._account_id : owner.email;
if (userId) {
- this._userId = userId;
+ this.userId = userId;
return;
}
}
- if (REPO_QUERY_PATTERN.test(this._query)) {
- this._repo = changes[0].project;
+ if (REPO_QUERY_PATTERN.test(this.query)) {
+ this.repo = changes[0].project;
}
}
- _computeHeaderClass(id?: string) {
- return id ? '' : 'hide';
+ // private but used in test
+ computePage() {
+ if (this.offset === undefined || this.changesPerPage === undefined) return;
+ return this.offset / this.changesPerPage + 1;
}
- _computePage(offset?: number, changesPerPage?: number) {
- if (offset === undefined || changesPerPage === undefined) return;
- return offset / changesPerPage + 1;
- }
-
- _computeLoggedIn(account?: AccountDetailInfo) {
- return !!(account && Object.keys(account).length > 0);
- }
-
- _handleToggleStar(e: CustomEvent<ChangeStarToggleStarDetail>) {
+ private handleToggleStar(e: CustomEvent<ChangeStarToggleStarDetail>) {
if (e.detail.starred) {
this.reporting.reportInteraction('change-starred-from-change-list');
}
@@ -306,16 +413,17 @@
);
}
- /**
- * Returns `this` as the visibility observer target for the keyboard shortcut
- * mixin to decide whether shortcuts should be enabled or not.
- */
- _computeObserverTarget() {
- return this;
+ private handleSelectedIndexChanged(e: ValueChangedEvent<number>) {
+ if (!this.viewState) return;
+ this.viewState.selectedChangeIndex = e.detail.value;
+ fire(this, 'view-state-changed', {value: this.viewState});
}
}
declare global {
+ interface HTMLElementEventMap {
+ 'view-state-changed': ValueChangedEvent<ChangeListViewState>;
+ }
interface HTMLElementTagNameMap {
'gr-change-list-view': GrChangeListView;
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_html.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_html.ts
deleted file mode 100644
index 355ef45..0000000
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_html.ts
+++ /dev/null
@@ -1,101 +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">
- :host {
- display: block;
- }
- .loading {
- color: var(--deemphasized-text-color);
- padding: var(--spacing-l);
- }
- gr-change-list {
- width: 100%;
- }
- gr-user-header,
- gr-repo-header {
- border-bottom: 1px solid var(--border-color);
- }
- nav {
- align-items: center;
- display: flex;
- height: 3rem;
- justify-content: flex-end;
- margin-right: 20px;
- }
- nav,
- iron-icon {
- color: var(--deemphasized-text-color);
- }
- iron-icon {
- height: 1.85rem;
- margin-left: 16px;
- width: 1.85rem;
- }
- .hide {
- display: none;
- }
- @media only screen and (max-width: 50em) {
- .loading,
- .error {
- padding: 0 var(--spacing-l);
- }
- }
- </style>
- <div class="loading" hidden$="[[!_loading]]" hidden="">Loading...</div>
- <div hidden$="[[_loading]]" hidden="">
- <gr-repo-header
- repo="[[_repo]]"
- class$="[[_computeHeaderClass(_repo)]]"
- ></gr-repo-header>
- <gr-user-header
- user-id="[[_userId]]"
- showDashboardLink=""
- logged-in="[[_loggedIn]]"
- class$="[[_computeHeaderClass(_userId)]]"
- ></gr-user-header>
- <gr-change-list
- account="[[account]]"
- changes="{{_changes}}"
- preferences="[[preferences]]"
- selected-index="{{viewState.selectedChangeIndex}}"
- show-star="[[_loggedIn]]"
- on-toggle-star="_handleToggleStar"
- observer-target="[[_computeObserverTarget()]]"
- ></gr-change-list>
- <nav class$="[[_computeNavClass(_loading)]]">
- Page [[_computePage(_offset, _changesPerPage)]]
- <a
- id="prevArrow"
- href$="[[_computeNavLink(_query, _offset, -1, _changesPerPage)]]"
- class$="[[_computePrevArrowClass(_offset)]]"
- >
- <iron-icon icon="gr-icons:chevron-left" aria-label="Older"> </iron-icon>
- </a>
- <a
- id="nextArrow"
- href$="[[_computeNavLink(_query, _offset, 1, _changesPerPage)]]"
- class$="[[_computeNextArrowClass(_changes)]]"
- >
- <iron-icon icon="gr-icons:chevron-right" aria-label="Newer">
- </iron-icon>
- </a>
- </nav>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js
deleted file mode 100644
index 03b6858..0000000
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.js
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 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 './gr-change-list-view.js';
-import {page} from '../../../utils/page-wrapper-utils.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import 'lodash/lodash.js';
-import {mockPromise, stubRestApi} from '../../../test/test-utils.js';
-
-const basicFixture = fixtureFromElement('gr-change-list-view');
-
-const CHANGE_ID = 'IcA3dAB3edAB9f60B8dcdA6ef71A75980e4B7127';
-const COMMIT_HASH = '12345678';
-
-suite('gr-change-list-view tests', () => {
- let element;
-
- setup(() => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(false));
- stubRestApi('getChanges').returns(Promise.resolve([]));
- stubRestApi('getAccountDetails').returns(Promise.resolve({}));
- stubRestApi('getAccountStatus').returns(Promise.resolve({}));
- element = basicFixture.instantiate();
- });
-
- teardown(async () => {
- await flush();
- });
-
- test('_computePage', () => {
- assert.equal(element._computePage(0, 25), 1);
- assert.equal(element._computePage(50, 25), 3);
- });
-
- test('_limitFor', () => {
- const defaultLimit = 25;
- const _limitFor = q => element._limitFor(q, defaultLimit);
- assert.equal(_limitFor(''), defaultLimit);
- assert.equal(_limitFor('limit:10'), 10);
- assert.equal(_limitFor('xlimit:10'), defaultLimit);
- assert.equal(_limitFor('x(limit:10'), 10);
- });
-
- test('_computeNavLink', () => {
- const getUrlStub = sinon.stub(GerritNav, 'getUrlForSearchQuery')
- .returns('');
- const query = 'status:open';
- let offset = 0;
- let direction = 1;
- const changesPerPage = 5;
-
- element._computeNavLink(query, offset, direction, changesPerPage);
- assert.equal(getUrlStub.lastCall.args[1], 5);
-
- direction = -1;
- element._computeNavLink(query, offset, direction, changesPerPage);
- assert.equal(getUrlStub.lastCall.args[1], 0);
-
- offset = 5;
- direction = 1;
- element._computeNavLink(query, offset, direction, changesPerPage);
- assert.equal(getUrlStub.lastCall.args[1], 10);
- });
-
- test('_computePrevArrowClass', () => {
- let offset = 0;
- assert.equal(element._computePrevArrowClass(offset), 'hide');
- offset = 5;
- assert.equal(element._computePrevArrowClass(offset), '');
- });
-
- test('_computeNextArrowClass', () => {
- let changes = _.times(25, _.constant({_more_changes: true}));
- assert.equal(element._computeNextArrowClass(changes), '');
- changes = _.times(25, _.constant({}));
- assert.equal(element._computeNextArrowClass(changes), 'hide');
- });
-
- test('_computeNavClass', () => {
- let loading = true;
- assert.equal(element._computeNavClass(loading), 'hide');
- loading = false;
- assert.equal(element._computeNavClass(loading), 'hide');
- element._changes = [];
- assert.equal(element._computeNavClass(loading), 'hide');
- element._changes = _.times(5, _.constant({}));
- assert.equal(element._computeNavClass(loading), '');
- });
-
- test('_handleNextPage', () => {
- const showStub = sinon.stub(page, 'show');
- element._changesPerPage = 10;
- element.$.nextArrow.hidden = true;
- element._handleNextPage();
- assert.isFalse(showStub.called);
- element.$.nextArrow.hidden = false;
- element._handleNextPage();
- assert.isTrue(showStub.called);
- });
-
- test('_handlePreviousPage', () => {
- const showStub = sinon.stub(page, 'show');
- element._changesPerPage = 10;
- element.$.prevArrow.hidden = true;
- element._handlePreviousPage();
- assert.isFalse(showStub.called);
- element.$.prevArrow.hidden = false;
- element._handlePreviousPage();
- assert.isTrue(showStub.called);
- });
-
- test('_userId query', async () => {
- assert.isNull(element._userId);
- element._query = 'owner: foo@bar';
- element._changes = [{owner: {email: 'foo@bar'}}];
- await flush();
- assert.equal(element._userId, 'foo@bar');
-
- element._query = 'foo bar baz';
- element._changes = [{owner: {email: 'foo@bar'}}];
- assert.isNull(element._userId);
- });
-
- test('_userId query without email', async () => {
- assert.isNull(element._userId);
- element._query = 'owner: foo@bar';
- element._changes = [{owner: {}}];
- await flush();
- assert.isNull(element._userId);
- });
-
- test('_repo query', async () => {
- assert.isNull(element._repo);
- element._query = 'project: test-repo';
- element._changes = [{owner: {email: 'foo@bar'}, project: 'test-repo'}];
- await flush();
- assert.equal(element._repo, 'test-repo');
- element._query = 'foo bar baz';
- element._changes = [{owner: {email: 'foo@bar'}}];
- assert.isNull(element._repo);
- });
-
- test('_repo query with open status', async () => {
- assert.isNull(element._repo);
- element._query = 'project:test-repo status:open';
- element._changes = [{owner: {email: 'foo@bar'}, project: 'test-repo'}];
- await flush();
- assert.equal(element._repo, 'test-repo');
- element._query = 'foo bar baz';
- element._changes = [{owner: {email: 'foo@bar'}}];
- assert.isNull(element._repo);
- });
-
- suite('query based navigation', () => {
- setup(() => {
- });
-
- teardown(async () => {
- await flush();
- sinon.restore();
- });
-
- test('Searching for a change ID redirects to change', async () => {
- const change = {_number: 1};
- sinon.stub(element, '_getChanges')
- .returns(Promise.resolve([change]));
- const promise = mockPromise();
- sinon.stub(GerritNav, 'navigateToChange').callsFake(
- (url, opt) => {
- assert.equal(url, change);
- assert.isTrue(opt.redirect);
- promise.resolve();
- });
-
- element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
- await promise;
- });
-
- test('Searching for a change num redirects to change', async () => {
- const change = {_number: 1};
- sinon.stub(element, '_getChanges')
- .returns(Promise.resolve([change]));
- const promise = mockPromise();
- sinon.stub(GerritNav, 'navigateToChange').callsFake(
- (url, opt) => {
- assert.equal(url, change);
- assert.isTrue(opt.redirect);
- promise.resolve();
- });
-
- element.params = {view: GerritNav.View.SEARCH, query: '1'};
- await promise;
- });
-
- test('Commit hash redirects to change', async () => {
- const change = {_number: 1};
- sinon.stub(element, '_getChanges')
- .returns(Promise.resolve([change]));
- const promise = mockPromise();
- sinon.stub(GerritNav, 'navigateToChange').callsFake(
- (url, opt) => {
- assert.equal(url, change);
- assert.isTrue(opt.redirect);
- promise.resolve();
- });
-
- element.params = {view: GerritNav.View.SEARCH, query: COMMIT_HASH};
- await promise;
- });
-
- test('Searching for an invalid change ID searches', async () => {
- sinon.stub(element, '_getChanges')
- .returns(Promise.resolve([]));
- const stub = sinon.stub(GerritNav, 'navigateToChange');
-
- element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
- await flush();
-
- assert.isFalse(stub.called);
- });
-
- test('Change ID with multiple search results searches', async () => {
- sinon.stub(element, '_getChanges')
- .returns(Promise.resolve([{}, {}]));
- const stub = sinon.stub(GerritNav, 'navigateToChange');
-
- element.params = {view: GerritNav.View.SEARCH, query: CHANGE_ID};
- await flush();
-
- assert.isFalse(stub.called);
- });
- });
-});
-
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
new file mode 100644
index 0000000..0639620
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.ts
@@ -0,0 +1,308 @@
+/**
+ * @license
+ * Copyright (C) 2016 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 './gr-change-list-view';
+import {GrChangeListView} from './gr-change-list-view';
+import {page} from '../../../utils/page-wrapper-utils';
+import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import 'lodash/lodash';
+import {mockPromise, query, stubRestApi} from '../../../test/test-utils';
+import {createChange} from '../../../test/test-data-generators.js';
+import {
+ ChangeInfo,
+ EmailAddress,
+ NumericChangeId,
+ RepoName,
+} from '../../../api/rest-api.js';
+
+const basicFixture = fixtureFromElement('gr-change-list-view');
+
+const CHANGE_ID = 'IcA3dAB3edAB9f60B8dcdA6ef71A75980e4B7127';
+const COMMIT_HASH = '12345678';
+
+suite('gr-change-list-view tests', () => {
+ let element: GrChangeListView;
+
+ setup(async () => {
+ stubRestApi('getLoggedIn').returns(Promise.resolve(false));
+ stubRestApi('getChanges').returns(Promise.resolve([]));
+ stubRestApi('getAccountDetails').returns(Promise.resolve(undefined));
+ stubRestApi('getAccountStatus').returns(Promise.resolve(undefined));
+ element = basicFixture.instantiate();
+ await element.updateComplete;
+ });
+
+ teardown(async () => {
+ await element.updateComplete;
+ });
+
+ test('computePage', () => {
+ element.offset = 0;
+ element.changesPerPage = 25;
+ assert.equal(element.computePage(), 1);
+ element.offset = 50;
+ element.changesPerPage = 25;
+ assert.equal(element.computePage(), 3);
+ });
+
+ test('limitFor', () => {
+ const defaultLimit = 25;
+ const limitFor = (q: string) => element.limitFor(q, defaultLimit);
+ assert.equal(limitFor(''), defaultLimit);
+ assert.equal(limitFor('limit:10'), 10);
+ assert.equal(limitFor('xlimit:10'), defaultLimit);
+ assert.equal(limitFor('x(limit:10'), 10);
+ });
+
+ test('computeNavLink', () => {
+ const getUrlStub = sinon
+ .stub(GerritNav, 'getUrlForSearchQuery')
+ .returns('');
+ element.query = 'status:open';
+ element.offset = 0;
+ element.changesPerPage = 5;
+ let direction = 1;
+
+ element.computeNavLink(direction);
+ assert.equal(getUrlStub.lastCall.args[1], 5);
+
+ direction = -1;
+ element.computeNavLink(direction);
+ assert.equal(getUrlStub.lastCall.args[1], 0);
+
+ element.offset = 5;
+ direction = 1;
+ element.computeNavLink(direction);
+ assert.equal(getUrlStub.lastCall.args[1], 10);
+ });
+
+ test('prevArrow', async () => {
+ element.changes = _.times(25, _.constant(createChange())) as ChangeInfo[];
+ element.offset = 0;
+ element.loading = false;
+ await element.updateComplete;
+ assert.isNotOk(query(element, '#prevArrow'));
+
+ element.offset = 5;
+ await element.updateComplete;
+ assert.isOk(query(element, '#prevArrow'));
+ });
+
+ test('nextArrow', async () => {
+ element.changes = _.times(
+ 25,
+ _.constant({...createChange(), _more_changes: true})
+ ) as ChangeInfo[];
+ element.loading = false;
+ await element.updateComplete;
+ assert.isOk(query(element, '#nextArrow'));
+
+ element.changes = _.times(25, _.constant(createChange())) as ChangeInfo[];
+ await element.updateComplete;
+ assert.isNotOk(query(element, '#nextArrow'));
+ });
+
+ test('handleNextPage', async () => {
+ const showStub = sinon.stub(page, 'show');
+ element.changes = _.times(25, _.constant(createChange())) as ChangeInfo[];
+ element.changesPerPage = 10;
+ element.loading = false;
+ await element.updateComplete;
+ element.handleNextPage();
+ assert.isFalse(showStub.called);
+
+ element.changes = _.times(
+ 25,
+ _.constant({...createChange(), _more_changes: true})
+ ) as ChangeInfo[];
+ element.loading = false;
+ await element.updateComplete;
+ element.handleNextPage();
+ assert.isTrue(showStub.called);
+ });
+
+ test('handlePreviousPage', async () => {
+ const showStub = sinon.stub(page, 'show');
+ element.offset = 0;
+ element.changes = _.times(25, _.constant(createChange())) as ChangeInfo[];
+ element.changesPerPage = 10;
+ element.loading = false;
+ await element.updateComplete;
+ element.handlePreviousPage();
+ assert.isFalse(showStub.called);
+
+ element.offset = 25;
+ await element.updateComplete;
+ element.handlePreviousPage();
+ assert.isTrue(showStub.called);
+ });
+
+ test('userId query', async () => {
+ assert.isNull(element.userId);
+ element.query = 'owner: foo@bar';
+ element.changes = [
+ {...createChange(), owner: {email: 'foo@bar' as EmailAddress}},
+ ];
+ await element.updateComplete;
+ assert.equal(element.userId, 'foo@bar' as EmailAddress);
+
+ element.query = 'foo bar baz';
+ element.changes = [
+ {...createChange(), owner: {email: 'foo@bar' as EmailAddress}},
+ ];
+ await element.updateComplete;
+ assert.isNull(element.userId);
+ });
+
+ test('userId query without email', async () => {
+ assert.isNull(element.userId);
+ element.query = 'owner: foo@bar';
+ element.changes = [{...createChange(), owner: {}}];
+ await element.updateComplete;
+ assert.isNull(element.userId);
+ });
+
+ test('repo query', async () => {
+ assert.isNull(element.repo);
+ element.query = 'project: test-repo';
+ element.changes = [
+ {
+ ...createChange(),
+ owner: {email: 'foo@bar' as EmailAddress},
+ project: 'test-repo' as RepoName,
+ },
+ ];
+ await element.updateComplete;
+ assert.equal(element.repo, 'test-repo' as RepoName);
+
+ element.query = 'foo bar baz';
+ element.changes = [
+ {...createChange(), owner: {email: 'foo@bar' as EmailAddress}},
+ ];
+ await element.updateComplete;
+ assert.isNull(element.repo);
+ });
+
+ test('repo query with open status', async () => {
+ assert.isNull(element.repo);
+ element.query = 'project:test-repo status:open';
+ element.changes = [
+ {
+ ...createChange(),
+ owner: {email: 'foo@bar' as EmailAddress},
+ project: 'test-repo' as RepoName,
+ },
+ ];
+ await element.updateComplete;
+ assert.equal(element.repo, 'test-repo' as RepoName);
+
+ element.query = 'foo bar baz';
+ element.changes = [
+ {...createChange(), owner: {email: 'foo@bar' as EmailAddress}},
+ ];
+ await element.updateComplete;
+ assert.isNull(element.repo);
+ });
+
+ suite('query based navigation', () => {
+ setup(() => {});
+
+ teardown(async () => {
+ await element.updateComplete;
+ sinon.restore();
+ });
+
+ test('Searching for a change ID redirects to change', async () => {
+ const change = {...createChange(), _number: 1 as NumericChangeId};
+ sinon.stub(element, 'getChanges').returns(Promise.resolve([change]));
+ const promise = mockPromise();
+ sinon.stub(GerritNav, 'navigateToChange').callsFake((url, opt) => {
+ assert.equal(url, change);
+ assert.isTrue(opt!.redirect);
+ promise.resolve();
+ });
+
+ element.params = {
+ view: GerritNav.View.SEARCH,
+ query: CHANGE_ID,
+ offset: '',
+ };
+ await promise;
+ });
+
+ test('Searching for a change num redirects to change', async () => {
+ const change = {...createChange(), _number: 1 as NumericChangeId};
+ sinon.stub(element, 'getChanges').returns(Promise.resolve([change]));
+ const promise = mockPromise();
+ sinon.stub(GerritNav, 'navigateToChange').callsFake((url, opt) => {
+ assert.equal(url, change);
+ assert.isTrue(opt!.redirect);
+ promise.resolve();
+ });
+
+ element.params = {view: GerritNav.View.SEARCH, query: '1', offset: ''};
+ await promise;
+ });
+
+ test('Commit hash redirects to change', async () => {
+ const change = {...createChange(), _number: 1 as NumericChangeId};
+ sinon.stub(element, 'getChanges').returns(Promise.resolve([change]));
+ const promise = mockPromise();
+ sinon.stub(GerritNav, 'navigateToChange').callsFake((url, opt) => {
+ assert.equal(url, change);
+ assert.isTrue(opt!.redirect);
+ promise.resolve();
+ });
+
+ element.params = {
+ view: GerritNav.View.SEARCH,
+ query: COMMIT_HASH,
+ offset: '',
+ };
+ await promise;
+ });
+
+ test('Searching for an invalid change ID searches', async () => {
+ sinon.stub(element, 'getChanges').returns(Promise.resolve([]));
+ const stub = sinon.stub(GerritNav, 'navigateToChange');
+
+ element.params = {
+ view: GerritNav.View.SEARCH,
+ query: CHANGE_ID,
+ offset: '',
+ };
+ await element.updateComplete;
+
+ assert.isFalse(stub.called);
+ });
+
+ test('Change ID with multiple search results searches', async () => {
+ sinon.stub(element, 'getChanges').returns(Promise.resolve(undefined));
+ const stub = sinon.stub(GerritNav, 'navigateToChange');
+
+ element.params = {
+ view: GerritNav.View.SEARCH,
+ query: CHANGE_ID,
+ offset: '',
+ };
+ await element.updateComplete;
+
+ assert.isFalse(stub.called);
+ });
+ });
+});
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index 185a730..1185fe8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -15,31 +15,19 @@
* limitations under the License.
*/
-import '../../../styles/gr-change-list-styles';
-import '../../../styles/gr-font-styles';
-import '../../../styles/shared-styles';
import '../../shared/gr-cursor-manager/gr-cursor-manager';
import '../gr-change-list-item/gr-change-list-item';
+import {GrChangeListItem} from '../gr-change-list-item/gr-change-list-item';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
-import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-change-list_html';
import {getAppContext} from '../../../services/app-context';
import {
- KeyboardShortcutMixin,
- Shortcut,
- ShortcutListener,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
-import {
GerritNav,
- DashboardSection,
YOUR_TURN,
CLOSED,
} from '../../core/gr-navigation/gr-navigation';
import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {isOwner} from '../../../utils/change-util';
-import {customElement, property, observe} from '@polymer/decorators';
import {GrCursorManager} from '../../shared/gr-cursor-manager/gr-cursor-manager';
import {
AccountInfo,
@@ -48,15 +36,23 @@
PreferencesInput,
} from '../../../types/common';
import {hasAttention} from '../../../utils/attention-set-util';
-import {fireEvent, fireReload} from '../../../utils/event-util';
+import {fire, fireEvent, fireReload} from '../../../utils/event-util';
import {ScrollMode} from '../../../constants/constants';
-import {listen} from '../../../services/shortcuts/shortcuts-service';
import {
getRequirements,
showNewSubmitRequirements,
} from '../../../utils/label-util';
import {addGlobalShortcut, Key} from '../../../utils/dom-util';
import {unique} from '../../../utils/common-util';
+import {changeListStyles} from '../../../styles/gr-change-list-styles';
+import {fontStyles} from '../../../styles/gr-font-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, PropertyValues, html, css} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+import {ShortcutController} from '../../lit/shortcut-controller';
+import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+import {queryAll} from '../../../utils/common-util';
+import {ValueChangedEvent} from '../../../types/events';
const NUMBER_FIXED_COLUMNS = 3;
const CLOSED_STATUS = ['MERGED', 'ABANDONED'];
@@ -78,24 +74,15 @@
];
export interface ChangeListSection {
+ countLabel?: string;
+ isOutgoing?: boolean;
name?: string;
query?: string;
results: ChangeInfo[];
}
-export interface GrChangeList {
- $: {};
-}
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = KeyboardShortcutMixin(PolymerElement);
-
@customElement('gr-change-list')
-export class GrChangeList extends base {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrChangeList extends LitElement {
/**
* Fired when next page key shortcut was pressed.
*
@@ -115,7 +102,7 @@
@property({type: Object})
account: AccountInfo | undefined = undefined;
- @property({type: Array, observer: '_changesChanged'})
+ @property({type: Array})
changes?: ChangeInfo[];
/**
@@ -125,13 +112,9 @@
@property({type: Array})
sections: ChangeListSection[] = [];
- @property({type: Array, computed: '_computeLabelNames(sections)'})
- labelNames?: string[];
+ @state() private dynamicHeaderEndpoints?: string[];
- @property({type: Array})
- _dynamicHeaderEndpoints?: string[];
-
- @property({type: Number, notify: true})
+ @property({type: Number})
selectedIndex?: number;
@property({type: Boolean})
@@ -155,24 +138,14 @@
@property({type: Boolean})
isCursorMoving = false;
- @property({type: Object})
- _config?: ServerInfo;
+ // private but used in test
+ @state() config?: ServerInfo;
private readonly flagsService = getAppContext().flagsService;
private readonly restApiService = getAppContext().restApiService;
- override keyboardShortcuts(): ShortcutListener[] {
- return [
- listen(Shortcut.CURSOR_NEXT_CHANGE, _ => this._nextChange()),
- listen(Shortcut.CURSOR_PREV_CHANGE, _ => this._prevChange()),
- listen(Shortcut.NEXT_PAGE, _ => this._nextPage()),
- listen(Shortcut.PREV_PAGE, _ => this._prevPage()),
- listen(Shortcut.OPEN_CHANGE, _ => this.openChange()),
- listen(Shortcut.TOGGLE_CHANGE_STAR, _ => this._toggleChangeStar()),
- listen(Shortcut.REFRESH_CHANGE_LIST, _ => this._refreshChangeList()),
- ];
- }
+ private readonly shortcuts = new ShortcutController(this);
private cursor = new GrCursorManager();
@@ -180,22 +153,33 @@
super();
this.cursor.scrollMode = ScrollMode.KEEP_VISIBLE;
this.cursor.focusOnMove = true;
+ this.shortcuts.addAbstract(Shortcut.CURSOR_NEXT_CHANGE, () =>
+ this.nextChange()
+ );
+ this.shortcuts.addAbstract(Shortcut.CURSOR_PREV_CHANGE, () =>
+ this.prevChange()
+ );
+ this.shortcuts.addAbstract(Shortcut.NEXT_PAGE, () => this.nextPage());
+ this.shortcuts.addAbstract(Shortcut.PREV_PAGE, () => this.prevPage());
+ this.shortcuts.addAbstract(Shortcut.OPEN_CHANGE, () => this.openChange());
+ this.shortcuts.addAbstract(Shortcut.TOGGLE_CHANGE_STAR, () =>
+ this.toggleChangeStar()
+ );
+ this.shortcuts.addAbstract(Shortcut.REFRESH_CHANGE_LIST, () =>
+ this.refreshChangeList()
+ );
addGlobalShortcut({key: Key.ENTER}, () => this.openChange());
}
- override ready() {
- super.ready();
- this.restApiService.getConfig().then(config => {
- this._config = config;
- });
- }
-
override connectedCallback() {
super.connectedCallback();
+ this.restApiService.getConfig().then(config => {
+ this.config = config;
+ });
getPluginLoader()
.awaitPluginsLoaded()
.then(() => {
- this._dynamicHeaderEndpoints =
+ this.dynamicHeaderEndpoints =
getPluginEndpoints().getDynamicEndpoints('change-list-header');
});
}
@@ -205,37 +189,259 @@
super.disconnectedCallback();
}
- _lowerCase(column: string) {
- return column.toLowerCase();
+ static override get styles() {
+ return [
+ changeListStyles,
+ fontStyles,
+ sharedStyles,
+ css`
+ #changeList {
+ border-collapse: collapse;
+ width: 100%;
+ }
+ .section-count-label {
+ color: var(--deemphasized-text-color);
+ font-family: var(--font-family);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-normal);
+ line-height: var(--line-height-small);
+ }
+ a.section-title:hover {
+ text-decoration: none;
+ }
+ a.section-title:hover .section-count-label {
+ text-decoration: none;
+ }
+ a.section-title:hover .section-name {
+ text-decoration: underline;
+ }
+ `,
+ ];
}
- @observe('account', 'preferences', '_config', 'sections')
- _computePreferences(
- account?: AccountInfo,
- preferences?: PreferencesInput,
- config?: ServerInfo,
- sections?: ChangeListSection[]
+ override render() {
+ const labelNames = this.computeLabelNames(this.sections);
+ return html`
+ <table id="changeList">
+ ${this.sections.map((changeSection, sectionIndex) =>
+ this.renderSections(changeSection, sectionIndex, labelNames)
+ )}
+ </table>
+ `;
+ }
+
+ private renderSections(
+ changeSection: ChangeListSection,
+ sectionIndex: number,
+ labelNames: string[]
) {
- if (!config) {
- return;
+ return html`
+ ${this.renderSectionHeader(changeSection, labelNames)}
+ <tbody class="groupContent">
+ ${this.isEmpty(changeSection)
+ ? this.renderNoChangesRow(changeSection, labelNames)
+ : this.renderColumnHeaders(changeSection, labelNames)}
+ ${changeSection.results.map((change, index) =>
+ this.renderChangeRow(
+ changeSection,
+ change,
+ index,
+ sectionIndex,
+ labelNames
+ )
+ )}
+ </tbody>
+ `;
+ }
+
+ private renderSectionHeader(
+ changeSection: ChangeListSection,
+ labelNames: string[]
+ ) {
+ if (!changeSection.name) return;
+
+ return html`
+ <tbody>
+ <tr class="groupHeader">
+ <td aria-hidden="true" class="leftPadding"></td>
+ <td aria-hidden="true" class="star" ?hidden=${!this.showStar}></td>
+ <td
+ class="cell"
+ colspan="${this.computeColspan(changeSection, labelNames)}"
+ >
+ <h2 class="heading-3">
+ <a
+ href="${this.sectionHref(changeSection.query)}"
+ class="section-title"
+ >
+ <span class="section-name">${changeSection.name}</span>
+ <span class="section-count-label"
+ >${changeSection.countLabel}</span
+ >
+ </a>
+ </h2>
+ </td>
+ </tr>
+ </tbody>
+ `;
+ }
+
+ private renderNoChangesRow(
+ changeSection: ChangeListSection,
+ labelNames: string[]
+ ) {
+ return html`
+ <tr class="noChanges">
+ <td class="leftPadding" ?aria-hidden="true"></td>
+ <td
+ class="star"
+ ?aria-hidden=${!this.showStar}
+ ?hidden=${!this.showStar}
+ ></td>
+ <td
+ class="cell"
+ colspan="${this.computeColspan(changeSection, labelNames)}"
+ >
+ ${this.getSpecialEmptySlot(changeSection)
+ ? html`<slot
+ name="${this.getSpecialEmptySlot(changeSection)}"
+ ></slot>`
+ : 'No changes'}
+ </td>
+ </tr>
+ `;
+ }
+
+ private renderColumnHeaders(
+ changeSection: ChangeListSection,
+ labelNames: string[]
+ ) {
+ return html`
+ <tr class="groupTitle">
+ <td class="leftPadding" ?aria-hidden="true"></td>
+ <td
+ class="star"
+ aria-label="Star status column"
+ ?hidden=${!this.showStar}
+ ></td>
+ <td class="number" ?hidden=${!this.showNumber}>#</td>
+ ${this.computeColumns(changeSection).map(item =>
+ this.renderHeaderCell(item)
+ )}
+ ${labelNames?.map(labelName => this.renderLabelHeader(labelName))}
+ ${this.dynamicHeaderEndpoints?.map(pluginHeader =>
+ this.renderEndpointHeader(pluginHeader)
+ )}
+ </tr>
+ `;
+ }
+
+ private renderHeaderCell(item: string) {
+ return html`<td class="${item.toLowerCase()}">${item}</td>`;
+ }
+
+ private renderLabelHeader(labelName: string) {
+ return html`
+ <td class="label" title="${labelName}">
+ ${this.computeLabelShortcut(labelName)}
+ </td>
+ `;
+ }
+
+ private renderEndpointHeader(pluginHeader: string) {
+ return html`
+ <td class="endpoint">
+ <gr-endpoint-decorator .name="${pluginHeader}"></gr-endpoint-decorator>
+ </td>
+ `;
+ }
+
+ private renderChangeRow(
+ changeSection: ChangeListSection,
+ change: ChangeInfo,
+ index: number,
+ sectionIndex: number,
+ labelNames: string[]
+ ) {
+ const ariaLabel = this.computeAriaLabel(change, changeSection.name);
+ const highlight = this.computeItemHighlight(
+ this.account,
+ change,
+ changeSection.name
+ );
+ const selected = this.computeItemSelected(
+ sectionIndex,
+ index,
+ this.selectedIndex
+ );
+ const tabindex = this.computeTabIndex(
+ sectionIndex,
+ index,
+ this.isCursorMoving,
+ this.selectedIndex
+ );
+ const visibleChangeTableColumns = this.computeColumns(changeSection);
+ return html`
+ <gr-change-list-item
+ .account=${this.account}
+ ?selected=${selected}
+ .highlight=${highlight}
+ .change=${change}
+ .config=${this.config}
+ .sectionName=${changeSection.name}
+ .visibleChangeTableColumns=${visibleChangeTableColumns}
+ .showNumber=${this.showNumber}
+ .showStar=${this.showStar}
+ ?tabindex=${tabindex}
+ .labelNames=${labelNames}
+ aria-label=${ariaLabel}
+ ></gr-change-list-item>
+ `;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (
+ changedProperties.has('account') ||
+ changedProperties.has('preferences') ||
+ changedProperties.has('config') ||
+ changedProperties.has('sections')
+ ) {
+ this.computePreferences();
}
- const changes = (sections ?? []).map(section => section.results).flat();
+ if (changedProperties.has('changes')) {
+ this.changesChanged();
+ }
+ }
+
+ override updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('sections')) {
+ this.sectionsChanged();
+ }
+ }
+
+ private computePreferences() {
+ if (!this.config) return;
+
+ const changes = (this.sections ?? [])
+ .map(section => section.results)
+ .flat();
this.changeTableColumns = columnNames;
this.showNumber = false;
this.visibleChangeTableColumns = this.changeTableColumns.filter(col =>
- this._isColumnEnabled(col, config, changes)
+ this._isColumnEnabled(col, this.config, changes)
);
- if (account && preferences) {
- this.showNumber = !!(
- preferences && preferences.legacycid_in_change_table
- );
- if (preferences.change_table && preferences.change_table.length > 0) {
- const prefColumns = preferences.change_table.map(column =>
+ if (this.account && this.preferences) {
+ this.showNumber = !!this.preferences?.legacycid_in_change_table;
+ if (
+ this.preferences?.change_table &&
+ this.preferences.change_table.length > 0
+ ) {
+ const prefColumns = this.preferences.change_table.map(column =>
column === 'Project' ? 'Repo' : column
);
this.visibleChangeTableColumns = prefColumns.filter(col =>
- this._isColumnEnabled(col, config, changes)
+ this._isColumnEnabled(col, this.config, changes)
);
}
}
@@ -270,12 +476,9 @@
*
* @param visibleColumns are the columns according to configs and user prefs
*/
- _computeColumns(
- section?: ChangeListSection,
- visibleColumns?: string[]
- ): string[] {
- if (!section || !visibleColumns) return [];
- const cols = [...visibleColumns];
+ private computeColumns(section?: ChangeListSection): string[] {
+ if (!section || !this.visibleChangeTableColumns) return [];
+ const cols = [...this.visibleChangeTableColumns];
const updatedIndex = cols.indexOf('Updated');
if (section.name === YOUR_TURN.name && updatedIndex !== -1) {
cols[updatedIndex] = 'Waiting';
@@ -286,20 +489,16 @@
return cols;
}
- _computeColspan(
- section?: ChangeListSection,
- visibleColumns?: string[],
- labelNames?: string[]
- ) {
- const cols = this._computeColumns(section, visibleColumns);
+ // private but used in test
+ computeColspan(section?: ChangeListSection, labelNames?: string[]) {
+ const cols = this.computeColumns(section);
if (!cols || !labelNames) return 1;
return cols.length + labelNames.length + NUMBER_FIXED_COLUMNS;
}
- _computeLabelNames(sections: ChangeListSection[]) {
- if (!sections) {
- return [];
- }
+ // private but used in test
+ computeLabelNames(sections: ChangeListSection[]) {
+ if (!sections) return [];
let labels: string[] = [];
const nonExistingLabel = function (item: string) {
return !labels.includes(item);
@@ -331,7 +530,8 @@
return labels.sort();
}
- _computeLabelShortcut(labelName: string) {
+ // private but used in test
+ computeLabelShortcut(labelName: string) {
if (labelName.startsWith(LABEL_PREFIX_INVALID_PROLOG)) {
labelName = labelName.slice(LABEL_PREFIX_INVALID_PROLOG.length);
}
@@ -346,11 +546,12 @@
.slice(0, MAX_SHORTCUT_CHARS);
}
- _changesChanged(changes: ChangeInfo[]) {
- this.sections = changes ? [{results: changes}] : [];
+ private changesChanged() {
+ this.sections = this.changes ? [{results: this.changes}] : [];
}
- _processQuery(query: string) {
+ // private but used in test
+ processQuery(query: string) {
let tokens = query.split(' ');
const invalidTokens = ['limit:', 'age:', '-age:'];
tokens = tokens.filter(
@@ -360,19 +561,22 @@
return tokens.join(' ');
}
- _sectionHref(query: string) {
- return GerritNav.getUrlForSearchQuery(this._processQuery(query));
+ private sectionHref(query?: string) {
+ if (!query) return;
+ return GerritNav.getUrlForSearchQuery(this.processQuery(query));
}
/**
* Maps an index local to a particular section to the absolute index
* across all the changes on the page.
*
+ * private but used in test
+ *
* @param sectionIndex index of section
* @param localIndex index of row within section
* @return absolute index of row in the aggregate dashboard
*/
- _computeItemAbsoluteIndex(sectionIndex: number, localIndex: number) {
+ computeItemAbsoluteIndex(sectionIndex: number, localIndex: number) {
let idx = 0;
for (let i = 0; i < sectionIndex; i++) {
idx += this.sections[i].results.length;
@@ -380,28 +584,28 @@
return idx + localIndex;
}
- _computeItemSelected(
+ private computeItemSelected(
sectionIndex: number,
index: number,
- selectedIndex: number
+ selectedIndex?: number
) {
- const idx = this._computeItemAbsoluteIndex(sectionIndex, index);
+ const idx = this.computeItemAbsoluteIndex(sectionIndex, index);
return idx === selectedIndex;
}
- _computeTabIndex(
+ private computeTabIndex(
sectionIndex: number,
index: number,
- selectedIndex: number,
- isCursorMoving: boolean
+ isCursorMoving: boolean,
+ selectedIndex?: number
) {
if (isCursorMoving) return 0;
- return this._computeItemSelected(sectionIndex, index, selectedIndex)
+ return this.computeItemSelected(sectionIndex, index, selectedIndex)
? 0
: undefined;
}
- _computeItemHighlight(
+ private computeItemHighlight(
account?: AccountInfo,
change?: ChangeInfo,
sectionName?: string
@@ -415,48 +619,45 @@
);
}
- _nextChange() {
+ private nextChange() {
this.isCursorMoving = true;
this.cursor.next();
this.isCursorMoving = false;
this.selectedIndex = this.cursor.index;
+ fire(this, 'selected-index-changed', {value: this.cursor.index});
}
- _prevChange() {
+ private prevChange() {
this.isCursorMoving = true;
this.cursor.previous();
this.isCursorMoving = false;
this.selectedIndex = this.cursor.index;
+ fire(this, 'selected-index-changed', {value: this.cursor.index});
}
- openChange() {
- const change = this._changeForIndex(this.selectedIndex);
+ private openChange() {
+ const change = this.changeForIndex(this.selectedIndex);
if (change) GerritNav.navigateToChange(change);
}
- _nextPage() {
+ private nextPage() {
fireEvent(this, 'next-page');
}
- _prevPage() {
- this.dispatchEvent(
- new CustomEvent('previous-page', {
- composed: true,
- bubbles: true,
- })
- );
+ private prevPage() {
+ fireEvent(this, 'previous-page');
}
- _refreshChangeList() {
+ private refreshChangeList() {
fireReload(this);
}
- _toggleChangeStar() {
- this._toggleStarForIndex(this.selectedIndex);
+ private toggleChangeStar() {
+ this.toggleStarForIndex(this.selectedIndex);
}
- _toggleStarForIndex(index?: number) {
- const changeEls = this._getListItems();
+ private toggleStarForIndex(index?: number) {
+ const changeEls = this.getListItems();
if (index === undefined || index >= changeEls.length || !changeEls[index]) {
return;
}
@@ -466,46 +667,47 @@
if (grChangeStar) grChangeStar.toggleStar();
}
- _changeForIndex(index?: number) {
- const changeEls = this._getListItems();
+ private changeForIndex(index?: number) {
+ const changeEls = this.getListItems();
if (index !== undefined && index < changeEls.length && changeEls[index]) {
return changeEls[index].change;
}
return null;
}
- _getListItems() {
- const items = this.root?.querySelectorAll('gr-change-list-item');
+ private getListItems() {
+ const items = queryAll<GrChangeListItem>(this, 'gr-change-list-item');
return !items ? [] : Array.from(items);
}
- @observe('sections.*')
- _sectionsChanged() {
- // Flush DOM operations so that the list item elements will be loaded.
- afterNextRender(this, () => {
- this.cursor.stops = this._getListItems();
- this.cursor.moveToStart();
- if (this.selectedIndex) this.cursor.setCursorAtIndex(this.selectedIndex);
- });
+ private sectionsChanged() {
+ this.cursor.stops = this.getListItems();
+ this.cursor.moveToStart();
+ if (this.selectedIndex) this.cursor.setCursorAtIndex(this.selectedIndex);
}
- _getSpecialEmptySlot(section: DashboardSection) {
+ // private but used in test
+ getSpecialEmptySlot(section: ChangeListSection) {
if (section.isOutgoing) return 'empty-outgoing';
if (section.name === YOUR_TURN.name) return 'empty-your-turn';
return '';
}
- _isEmpty(section: DashboardSection) {
+ // private but used in test
+ isEmpty(section: ChangeListSection) {
return !section.results?.length;
}
- _computeAriaLabel(change?: ChangeInfo, sectionName?: string) {
+ private computeAriaLabel(change?: ChangeInfo, sectionName?: string) {
if (!change) return '';
return change.subject + (sectionName ? `, section: ${sectionName}` : '');
}
}
declare global {
+ interface HTMLElementEventMap {
+ 'selected-index-changed': ValueChangedEvent<number>;
+ }
interface HTMLElementTagNameMap {
'gr-change-list': GrChangeList;
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
deleted file mode 100644
index 77320b9..0000000
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_html.ts
+++ /dev/null
@@ -1,165 +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-font-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="gr-change-list-styles">
- #changeList {
- border-collapse: collapse;
- width: 100%;
- }
- .section-count-label {
- color: var(--deemphasized-text-color);
- font-family: var(--font-family);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-normal);
- line-height: var(--line-height-small);
- }
- a.section-title:hover {
- text-decoration: none;
- }
- a.section-title:hover .section-count-label {
- text-decoration: none;
- }
- a.section-title:hover .section-name {
- text-decoration: underline;
- }
- </style>
- <table id="changeList">
- <template
- is="dom-repeat"
- items="[[sections]]"
- as="changeSection"
- index-as="sectionIndex"
- >
- <template is="dom-if" if="[[changeSection.name]]">
- <tbody>
- <tr class="groupHeader">
- <td aria-hidden="true" class="leftPadding"></td>
- <td
- aria-hidden="true"
- class="star"
- hidden$="[[!showStar]]"
- hidden=""
- ></td>
- <td
- class="cell"
- colspan$="[[_computeColspan(changeSection, visibleChangeTableColumns, labelNames)]]"
- >
- <h2 class="heading-3">
- <a
- href$="[[_sectionHref(changeSection.query)]]"
- class="section-title"
- >
- <span class="section-name">[[changeSection.name]]</span>
- <span class="section-count-label"
- >[[changeSection.countLabel]]</span
- >
- </a>
- </h2>
- </td>
- </tr>
- </tbody>
- </template>
- <tbody class="groupContent">
- <template is="dom-if" if="[[_isEmpty(changeSection)]]">
- <tr class="noChanges">
- <td aria-hidden="true" class="leftPadding"></td>
- <td
- aria-hidden="[[!showStar]]"
- class="star"
- hidden$="[[!showStar]]"
- ></td>
- <td
- class="cell"
- colspan$="[[_computeColspan(changeSection, visibleChangeTableColumns, labelNames)]]"
- >
- <template
- is="dom-if"
- if="[[_getSpecialEmptySlot(changeSection)]]"
- >
- <slot name="[[_getSpecialEmptySlot(changeSection)]]"></slot>
- </template>
- <template
- is="dom-if"
- if="[[!_getSpecialEmptySlot(changeSection)]]"
- >
- No changes
- </template>
- </td>
- </tr>
- </template>
- <template is="dom-if" if="[[!_isEmpty(changeSection)]]">
- <tr class="groupTitle">
- <td aria-hidden="true" class="leftPadding"></td>
- <td
- aria-label="Star status column"
- class="star"
- hidden$="[[!showStar]]"
- hidden=""
- ></td>
- <td class="number" hidden$="[[!showNumber]]" hidden="">#</td>
- <template
- is="dom-repeat"
- items="[[_computeColumns(changeSection, visibleChangeTableColumns)]]"
- as="item"
- >
- <td class$="[[_lowerCase(item)]]">[[item]]</td>
- </template>
- <template is="dom-repeat" items="[[labelNames]]" as="labelName">
- <td class="label" title$="[[labelName]]">
- [[_computeLabelShortcut(labelName)]]
- </td>
- </template>
- <template
- is="dom-repeat"
- items="[[_dynamicHeaderEndpoints]]"
- as="pluginHeader"
- >
- <td class="endpoint">
- <gr-endpoint-decorator name$="[[pluginHeader]]">
- </gr-endpoint-decorator>
- </td>
- </template>
- </tr>
- </template>
- <template is="dom-repeat" items="[[changeSection.results]]" as="change">
- <gr-change-list-item
- account="[[account]]"
- selected$="[[_computeItemSelected(sectionIndex, index, selectedIndex)]]"
- highlight$="[[_computeItemHighlight(account, change, changeSection.name)]]"
- change="[[change]]"
- config="[[_config]]"
- section-name="[[changeSection.name]]"
- visible-change-table-columns="[[_computeColumns(changeSection, visibleChangeTableColumns)]]"
- show-number="[[showNumber]]"
- show-star="[[showStar]]"
- tabindex$="[[_computeTabIndex(sectionIndex, index, selectedIndex, isCursorMoving)]]"
- label-names="[[labelNames]]"
- aria-label$="[[_computeAriaLabel(change, changeSection.name)]]"
- ></gr-change-list-item>
- </template>
- </tbody>
- </template>
- </table>
-`;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
index ee15b44..50708c0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
@@ -17,10 +17,8 @@
import '../../../test/common-test-setup-karma';
import './gr-change-list';
import {GrChangeList} from './gr-change-list';
-import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {
- mockPromise,
pressKey,
query,
queryAll,
@@ -47,11 +45,12 @@
});
suite('test show change number not logged in', () => {
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
element.account = undefined;
element.preferences = undefined;
- element._config = createServerInfo();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('show number disabled', () => {
@@ -60,7 +59,7 @@
});
suite('test show change number preference enabled', () => {
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
element.preferences = {
legacycid_in_change_table: true,
@@ -68,8 +67,8 @@
change_table: [],
};
element.account = {_account_id: 1001 as AccountId};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('show number enabled', () => {
@@ -78,7 +77,7 @@
});
suite('test show change number preference disabled', () => {
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
// legacycid_in_change_table is not set when false.
element.preferences = {
@@ -86,8 +85,8 @@
change_table: [],
};
element.account = {_account_id: 1001 as AccountId};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('show number disabled', () => {
@@ -97,7 +96,7 @@
test('computed fields', () => {
assert.equal(
- element._computeLabelNames([
+ element.computeLabelNames([
{
results: [
{...createChange(), _number: 0 as NumericChangeId, labels: {}},
@@ -107,7 +106,7 @@
0
);
assert.equal(
- element._computeLabelNames([
+ element.computeLabelNames([
{
results: [
{
@@ -137,49 +136,43 @@
3
);
- assert.equal(element._computeLabelShortcut('Code-Review'), 'CR');
- assert.equal(element._computeLabelShortcut('Verified'), 'V');
- assert.equal(element._computeLabelShortcut('Library-Compliance'), 'LC');
- assert.equal(element._computeLabelShortcut('PolyGerrit-Review'), 'PR');
- assert.equal(element._computeLabelShortcut('polygerrit-review'), 'PR');
+ assert.equal(element.computeLabelShortcut('Code-Review'), 'CR');
+ assert.equal(element.computeLabelShortcut('Verified'), 'V');
+ assert.equal(element.computeLabelShortcut('Library-Compliance'), 'LC');
+ assert.equal(element.computeLabelShortcut('PolyGerrit-Review'), 'PR');
+ assert.equal(element.computeLabelShortcut('polygerrit-review'), 'PR');
assert.equal(
- element._computeLabelShortcut(
- 'Invalid-Prolog-Rules-Label-Name--Verified'
- ),
+ element.computeLabelShortcut('Invalid-Prolog-Rules-Label-Name--Verified'),
'V'
);
- assert.equal(element._computeLabelShortcut('Some-Special-Label-7'), 'SSL7');
+ assert.equal(element.computeLabelShortcut('Some-Special-Label-7'), 'SSL7');
assert.equal(
- element._computeLabelShortcut('--Too----many----dashes---'),
+ element.computeLabelShortcut('--Too----many----dashes---'),
'TMD'
);
assert.equal(
- element._computeLabelShortcut(
+ element.computeLabelShortcut(
'Really-rather-entirely-too-long-of-a-label-name'
),
'RRETL'
);
});
- test('colspans', () => {
+ test('colspans', async () => {
element.sections = [{results: [{...createChange()}]}];
- flush();
+ await element.updateComplete;
const tdItemCount = queryAll<HTMLTableElement>(element, 'td').length;
- const changeTableColumns: string[] | undefined = [];
+ element.visibleChangeTableColumns = [];
const labelNames: string[] | undefined = [];
assert.equal(
tdItemCount,
- element._computeColspan(
- {results: [{...createChange()}]},
- changeTableColumns,
- labelNames
- )
+ element.computeColspan({results: [{...createChange()}]}, labelNames)
);
});
test('keyboard shortcuts', async () => {
- sinon.stub(element, '_computeLabelNames');
+ sinon.stub(element, 'computeLabelNames');
element.sections = [{results: new Array(1)}, {results: new Array(2)}];
element.selectedIndex = 0;
element.changes = [
@@ -187,12 +180,7 @@
{...createChange(), _number: 1 as NumericChangeId},
{...createChange(), _number: 2 as NumericChangeId},
];
- await flush();
- const promise = mockPromise();
- afterNextRender(element, () => {
- promise.resolve();
- });
- await promise;
+ await element.updateComplete;
const elementItems = queryAll<GrChangeListItem>(
element,
'gr-change-list-item'
@@ -201,15 +189,18 @@
assert.isTrue(elementItems[0].hasAttribute('selected'));
pressKey(element, 'j');
+ await element.updateComplete;
assert.equal(element.selectedIndex, 1);
assert.isTrue(elementItems[1].hasAttribute('selected'));
pressKey(element, 'j');
+ await element.updateComplete;
assert.equal(element.selectedIndex, 2);
assert.isTrue(elementItems[2].hasAttribute('selected'));
const navStub = sinon.stub(GerritNav, 'navigateToChange');
assert.equal(element.selectedIndex, 2);
pressKey(element, Key.ENTER);
+ await element.updateComplete;
assert.deepEqual(
navStub.lastCall.args[0],
{...createChange(), _number: 2 as NumericChangeId},
@@ -217,8 +208,10 @@
);
pressKey(element, 'k');
+ await element.updateComplete;
assert.equal(element.selectedIndex, 1);
pressKey(element, Key.ENTER);
+ await element.updateComplete;
assert.deepEqual(
navStub.lastCall.args[0],
{...createChange(), _number: 1 as NumericChangeId},
@@ -231,9 +224,9 @@
assert.equal(element.selectedIndex, 0);
});
- test('no changes', () => {
+ test('no changes', async () => {
element.changes = [];
- flush();
+ await element.updateComplete;
const listItems = queryAll<GrChangeListItem>(
element,
'gr-change-list-item'
@@ -246,9 +239,9 @@
assert.ok(noChangesMsg);
});
- test('empty sections', () => {
+ test('empty sections', async () => {
element.sections = [{results: []}, {results: []}];
- flush();
+ await element.updateComplete;
const listItems = queryAll<GrChangeListItem>(
element,
'gr-change-list-item'
@@ -261,8 +254,8 @@
suite('empty section', () => {
test('not shown on empty non-outgoing sections', () => {
const section = {name: 'test', query: 'test', results: []};
- assert.isTrue(element._isEmpty(section));
- assert.equal(element._getSpecialEmptySlot(section), '');
+ assert.isTrue(element.isEmpty(section));
+ assert.equal(element.getSpecialEmptySlot(section), '');
});
test('shown on empty outgoing sections', () => {
@@ -272,14 +265,14 @@
results: [],
isOutgoing: true,
};
- assert.isTrue(element._isEmpty(section));
- assert.equal(element._getSpecialEmptySlot(section), 'empty-outgoing');
+ assert.isTrue(element.isEmpty(section));
+ assert.equal(element.getSpecialEmptySlot(section), 'empty-outgoing');
});
test('shown on empty outgoing sections', () => {
const section = {name: YOUR_TURN.name, query: 'test', results: []};
- assert.isTrue(element._isEmpty(section));
- assert.equal(element._getSpecialEmptySlot(section), 'empty-your-turn');
+ assert.isTrue(element.isEmpty(section));
+ assert.equal(element.getSpecialEmptySlot(section), 'empty-your-turn');
});
test('not shown on non-empty outgoing sections', () => {
@@ -295,14 +288,14 @@
},
],
};
- assert.isFalse(element._isEmpty(section));
+ assert.isFalse(element.isEmpty(section));
});
});
suite('empty column preference', () => {
let element: GrChangeList;
- setup(() => {
+ setup(async () => {
stubFlags('isEnabled').returns(true);
element = basicFixture.instantiate();
element.sections = [{results: [{...createChange()}]}];
@@ -312,8 +305,8 @@
time_format: TimeFormat.HHMM_12,
change_table: [],
};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('show number enabled', () => {
@@ -333,7 +326,7 @@
suite('full column preference', () => {
let element: GrChangeList;
- setup(() => {
+ setup(async () => {
stubFlags('isEnabled').returns(true);
element = basicFixture.instantiate();
element.sections = [{results: [{...createChange()}]}];
@@ -354,8 +347,8 @@
' Status ',
],
};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('all columns visible', () => {
@@ -371,7 +364,7 @@
suite('partial column preference', () => {
let element: GrChangeList;
- setup(() => {
+ setup(async () => {
stubFlags('isEnabled').returns(true);
element = basicFixture.instantiate();
element.sections = [{results: [{...createChange()}]}];
@@ -391,8 +384,8 @@
' Status ',
],
};
- element._config = createServerInfo();
- flush();
+ element.config = createServerInfo();
+ await element.updateComplete;
});
test('all columns except repo visible', () => {
@@ -417,7 +410,7 @@
/* This would only exist if somebody manually updated the config
file. */
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
element.account = {_account_id: 1001 as AccountId};
element.preferences = {
@@ -425,7 +418,7 @@
time_format: TimeFormat.HHMM_12,
change_table: ['Bad'],
};
- flush();
+ await element.updateComplete;
});
test('bad column does not exist', () => {
@@ -446,43 +439,43 @@
test('query without age and limit unchanged', () => {
const query = 'status:closed owner:me';
- assert.deepEqual(element._processQuery(query), query);
+ assert.deepEqual(element.processQuery(query), query);
});
test('query with age and limit', () => {
const query = 'status:closed age:1week limit:10 owner:me';
const expectedQuery = 'status:closed owner:me';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with age', () => {
const query = 'status:closed age:1week owner:me';
const expectedQuery = 'status:closed owner:me';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with limit', () => {
const query = 'status:closed limit:10 owner:me';
const expectedQuery = 'status:closed owner:me';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with age as value and not key', () => {
const query = 'status:closed random:age';
const expectedQuery = 'status:closed random:age';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with limit as value and not key', () => {
const query = 'status:closed random:limit';
const expectedQuery = 'status:closed random:limit';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
test('query with -age key', () => {
const query = 'status:closed -age:1week';
const expectedQuery = 'status:closed';
- assert.deepEqual(element._processQuery(query), expectedQuery);
+ assert.deepEqual(element.processQuery(query), expectedQuery);
});
});
@@ -518,12 +511,7 @@
],
},
];
- await flush();
- const promise = mockPromise();
- afterNextRender(element, () => {
- promise.resolve();
- });
- await promise;
+ await element.updateComplete;
const elementItems = queryAll<GrChangeListItem>(
element,
'gr-change-list-item'
@@ -565,23 +553,23 @@
);
});
- test('_computeItemAbsoluteIndex', () => {
- sinon.stub(element, '_computeLabelNames');
+ test('computeItemAbsoluteIndex', () => {
+ sinon.stub(element, 'computeLabelNames');
element.sections = [
{results: new Array(1)},
{results: new Array(2)},
{results: new Array(3)},
];
- assert.equal(element._computeItemAbsoluteIndex(0, 0), 0);
+ assert.equal(element.computeItemAbsoluteIndex(0, 0), 0);
// Out of range but no matter.
- assert.equal(element._computeItemAbsoluteIndex(0, 1), 1);
+ assert.equal(element.computeItemAbsoluteIndex(0, 1), 1);
- assert.equal(element._computeItemAbsoluteIndex(1, 0), 1);
- assert.equal(element._computeItemAbsoluteIndex(1, 1), 2);
- assert.equal(element._computeItemAbsoluteIndex(1, 2), 3);
- assert.equal(element._computeItemAbsoluteIndex(2, 0), 3);
- assert.equal(element._computeItemAbsoluteIndex(3, 0), 6);
+ assert.equal(element.computeItemAbsoluteIndex(1, 0), 1);
+ assert.equal(element.computeItemAbsoluteIndex(1, 1), 2);
+ assert.equal(element.computeItemAbsoluteIndex(1, 2), 3);
+ assert.equal(element.computeItemAbsoluteIndex(2, 0), 3);
+ assert.equal(element.computeItemAbsoluteIndex(3, 0), 6);
});
});
});
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index 9beaa58..1b04268 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -450,12 +450,8 @@
this.$.commandsDialog.open();
}
- /**
- * Returns `this` as the visibility observer target for the keyboard shortcut
- * mixin to decide whether shortcuts should be enabled or not.
- */
- _computeObserverTarget() {
- return this;
+ _handleSelectedIndexChanged(e: CustomEvent) {
+ this._selectedChangeIndex = Number(e.detail.value);
}
}
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
index a55befb..84cf6d9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
@@ -78,10 +78,10 @@
show-star=""
account="[[account]]"
preferences="[[preferences]]"
- selected-index="{{_selectedChangeIndex}}"
+ selected-index="[[_selectedChangeIndex]]"
sections="[[_results]]"
+ on-selected-index-changed="_handleSelectedIndexChanged"
on-toggle-star="_handleToggleStar"
- observer-target="[[_computeObserverTarget()]]"
>
<div id="emptyOutgoing" slot="empty-outgoing">
<template is="dom-if" if="[[_showNewUserHelp]]">
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
index a6ee55d..21d9b97 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
@@ -37,8 +37,7 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, html, css} from 'lit';
import {customElement, property, state} from 'lit/decorators';
-import {ValueChangedEvent} from '../../../types/events';
-import {fire, fireEvent} from '../../../utils/event-util';
+import {fireEvent} from '../../../utils/event-util';
type MainHeaderLink = RequireProperties<DropdownLink, 'url' | 'name'>;
@@ -103,9 +102,6 @@
]);
declare global {
- interface HTMLElementEventMap {
- 'search-query-changed': ValueChangedEvent;
- }
interface HTMLElementTagNameMap {
'gr-main-header': GrMainHeader;
}
@@ -383,9 +379,6 @@
label="Search for changes"
.searchQuery=${this.searchQuery}
.serverConfig=${this.serverConfig}
- @search-query-changed=${(e: ValueChangedEvent) => {
- this.handleSearchQueryBindValueChanged(e);
- }}
></gr-smart-search>
<gr-endpoint-decorator
class="hideOnMobile"
@@ -651,8 +644,4 @@
e.stopPropagation();
fireEvent(this, 'mobile-search');
}
-
- private handleSearchQueryBindValueChanged(e: ValueChangedEvent) {
- fire(this, 'search-query-changed', {value: e.detail.value});
- }
}
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
index 71ffe18..ed6b822 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.ts
@@ -26,7 +26,6 @@
import {getAppContext} from '../../../services/app-context';
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators';
-import {fire} from '../../../utils/event-util';
const MAX_AUTOCOMPLETE_RESULTS = 10;
const SELF_EXPRESSION = 'self';
@@ -73,9 +72,6 @@
@handle-search=${(e: CustomEvent<SearchBarHandleSearchDetail>) => {
this.handleSearch(e);
}}
- @value-changed=${(e: CustomEvent) => {
- this.handleSearchValueChanged(e);
- }}
></gr-search-bar>
`;
}
@@ -196,8 +192,4 @@
GerritNav.navigateToSearchQuery(input);
}
}
-
- private handleSearchValueChanged(e: CustomEvent) {
- fire(this, 'search-query-changed', {value: e.detail.value});
- }
}
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
index 773e22e..6e43fdc 100644
--- a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
@@ -279,7 +279,7 @@
: this.showAbove()
? 'aboveButton'
: 'belowButton';
- if (this.partialContent) {
+ if (this.group?.hasSkipGroup()) {
// Expanding content would require load of more data
text += ' (too large)';
}
@@ -356,7 +356,7 @@
return (e: Event) => {
assertIsDefined(this.group);
e.stopPropagation();
- if (type === ContextButtonType.ALL && this.partialContent) {
+ if (type === ContextButtonType.ALL && this.group?.hasSkipGroup()) {
fire(this, 'content-load-needed', {
lineRange: this.group.lineRange,
});
@@ -406,13 +406,6 @@
}
/**
- * Checks if the collapsed section contains unavailable content (skip chunks).
- */
- private get partialContent() {
- return this.group?.contextGroups.some(c => !!c.skip);
- }
-
- /**
* Creates a container div with block expansion buttons (above and/or below).
*/
private createBlockExpansionButtons() {
@@ -420,7 +413,7 @@
if (
!this.showPartialLinks() ||
!this.renderPreferences?.use_block_expansion ||
- this.partialContent
+ this.group?.hasSkipGroup()
) {
return undefined;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
index b913e3e6..7ab24ab 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -22,7 +22,7 @@
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-diff-builder-element_html';
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
-import {GrDiffBuilder} from './gr-diff-builder';
+import {DiffContextExpandedEventDetail, GrDiffBuilder} from './gr-diff-builder';
import {GrDiffBuilderSideBySide} from './gr-diff-builder-side-by-side';
import {GrDiffBuilderImage} from './gr-diff-builder-image';
import {GrDiffBuilderUnified} from './gr-diff-builder-unified';
@@ -42,12 +42,13 @@
} from '../gr-ranged-comment-layer/gr-ranged-comment-layer';
import {GrCoverageLayer} from '../gr-coverage-layer/gr-coverage-layer';
import {DiffViewMode, RenderPreferences} from '../../../api/diff';
-import {Side} from '../../../constants/constants';
+import {createDefaultDiffPrefs, Side} from '../../../constants/constants';
import {GrDiffLine, LineNumber} from '../gr-diff/gr-diff-line';
import {GrDiffGroup} from '../gr-diff/gr-diff-group';
import {PolymerSpliceChange} from '@polymer/polymer/interfaces';
import {getLineNumber, getSideByLineEl} from '../gr-diff/gr-diff-utils';
import {fireAlert, fireEvent} from '../../../utils/event-util';
+import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
const TRAILING_WHITESPACE_PATTERN = /\s+$/;
@@ -139,6 +140,12 @@
path?: string;
@property({type: Object})
+ prefs: DiffPreferencesInfo = createDefaultDiffPrefs();
+
+ @property({type: Object})
+ renderPrefs?: RenderPreferences;
+
+ @property({type: Object})
_builder?: GrDiffBuilder;
// This is written to only from the processor via property notify
@@ -193,6 +200,19 @@
@property({type: Object})
_cancelableRenderPromise: CancelablePromise<unknown> | null = null;
+ constructor() {
+ super();
+ afterNextRender(this, () => {
+ this.addEventListener(
+ 'diff-context-expanded',
+ (e: CustomEvent<DiffContextExpandedEventDetail>) => {
+ // Don't stop propagation. The host may listen for reporting or resizing.
+ this.rerenderSection(e.detail.groups, e.detail.section);
+ }
+ );
+ });
+ }
+
override disconnectedCallback() {
if (this._builder) {
this._builder.clear();
@@ -213,19 +233,15 @@
return coverageRanges.filter(range => range && range.side === 'right');
}
- render(
- keyLocations: KeyLocations,
- prefs: DiffPreferencesInfo,
- renderPrefs?: RenderPreferences
- ) {
+ render(keyLocations: KeyLocations) {
// Setting up annotation layers must happen after plugins are
// installed, and |render| satisfies the requirement, however,
// |attached| doesn't because in the diff view page, the element is
// attached before plugins are installed.
this._setupAnnotationLayers();
- this._showTabs = !!prefs.show_tabs;
- this._showTrailingWhitespace = !!prefs.show_whitespace_errors;
+ this._showTabs = this.prefs.show_tabs;
+ this._showTrailingWhitespace = this.prefs.show_whitespace_errors;
// Stop the processor if it's running.
this.cancel();
@@ -236,13 +252,16 @@
if (!this.diff) {
throw Error('Cannot render a diff without DiffInfo.');
}
- this._builder = this._getDiffBuilder(this.diff, prefs, renderPrefs);
+ this._builder = this._getDiffBuilder();
- this.$.processor.context = prefs.context;
+ this.$.processor.context = this.prefs.context;
this.$.processor.keyLocations = keyLocations;
this._clearDiffContent();
- this._builder.addColumns(this.diffElement, getLineNumberCellWidth(prefs));
+ this._builder.addColumns(
+ this.diffElement,
+ getLineNumberCellWidth(this.prefs)
+ );
const isBinary = !!(this.isImageDiff || this.diff.binary);
@@ -323,7 +342,10 @@
* @param newGroups The groups to be rendered in the place of the section.
* @param sectionEl The context section that should be expanded from.
*/
- rerenderSection(newGroups: readonly GrDiffGroup[], sectionEl: HTMLElement) {
+ private rerenderSection(
+ newGroups: readonly GrDiffGroup[],
+ sectionEl: HTMLElement
+ ) {
if (!this._builder) return;
const contextIndex = this._builder.getIndexOfSection(sectionEl);
@@ -348,20 +370,19 @@
throw Error(`Invalid preference value: ${pref}`);
}
- _getDiffBuilder(
- diff: DiffInfo,
- prefs: DiffPreferencesInfo,
- renderPrefs?: RenderPreferences
- ): GrDiffBuilder {
- if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
+ _getDiffBuilder(): GrDiffBuilder {
+ if (!this.diff) {
+ throw Error('Cannot render a diff without DiffInfo.');
+ }
+ if (isNaN(this.prefs.tab_size) || this.prefs.tab_size <= 0) {
this._handlePreferenceError('tab size');
}
- if (isNaN(prefs.line_length) || prefs.line_length <= 0) {
+ if (isNaN(this.prefs.line_length) || this.prefs.line_length <= 0) {
this._handlePreferenceError('diff width');
}
- const localPrefs = {...prefs};
+ const localPrefs = {...this.prefs};
if (this.path === COMMIT_MSG_PATH) {
// override line_length for commit msg the same way as
// in gr-diff
@@ -371,32 +392,32 @@
let builder = null;
if (this.isImageDiff) {
builder = new GrDiffBuilderImage(
- diff,
+ this.diff,
localPrefs,
this.diffElement,
this.baseImage,
this.revisionImage,
- renderPrefs,
+ this.renderPrefs,
this.useNewImageDiffUi
);
- } else if (diff.binary) {
+ } else if (this.diff.binary) {
// If the diff is binary, but not an image.
- return new GrDiffBuilderBinary(diff, localPrefs, this.diffElement);
+ return new GrDiffBuilderBinary(this.diff, localPrefs, this.diffElement);
} else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
builder = new GrDiffBuilderSideBySide(
- diff,
+ this.diff,
localPrefs,
this.diffElement,
this._layers,
- renderPrefs
+ this.renderPrefs
);
} else if (this.viewMode === DiffViewMode.UNIFIED) {
builder = new GrDiffBuilderUnified(
- diff,
+ this.diff,
localPrefs,
this.diffElement,
this._layers,
- renderPrefs
+ this.renderPrefs
);
}
if (!builder) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
index 413a292..9f42e9e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
@@ -16,9 +16,6 @@
*/
import '../../../test/common-test-setup-karma.js';
-import '../gr-diff/gr-diff-group.js';
-import './gr-diff-builder.js';
-import '../gr-context-controls/gr-context-controls.js';
import {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
import './gr-diff-builder-element.js';
import {stubBaseUrl} from '../../../test/test-utils.js';
@@ -31,6 +28,7 @@
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
import {DiffViewMode} from '../../../api/diff.js';
import {stubRestApi} from '../../../test/test-utils.js';
+import {afterNextRender} from '@polymer/polymer/lib/utils/render-status';
const basicFixture = fixtureFromTemplate(html`
<gr-diff-builder>
@@ -48,6 +46,14 @@
</gr-diff-builder>
`);
+// GrDiffBuilderElement forces these prefs to be set - tests that do not care
+// about these values can just set these defaults.
+const DEFAULT_PREFS = {
+ line_length: 10,
+ show_tabs: true,
+ tab_size: 4,
+};
+
suite('gr-diff-builder tests', () => {
let prefs;
let element;
@@ -61,11 +67,7 @@
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getProjectConfig').returns(Promise.resolve({}));
stubBaseUrl('/r');
- prefs = {
- line_length: 10,
- show_tabs: true,
- tab_size: 4,
- };
+ prefs = {...DEFAULT_PREFS};
builder = new GrDiffBuilder({content: []}, prefs);
});
@@ -142,18 +144,18 @@
test(`line_length used for regular files under ${mode}`, () => {
element.path = '/a.txt';
element.viewMode = mode;
- builder = element._getDiffBuilder(
- {}, {tab_size: 4, line_length: 50}
- );
+ element.diff = {};
+ element.prefs = {tab_size: 4, line_length: 50};
+ builder = element._getDiffBuilder();
assert.equal(builder._prefs.line_length, 50);
});
test(`line_length ignored for commit msg under ${mode}`, () => {
element.path = '/COMMIT_MSG';
element.viewMode = mode;
- builder = element._getDiffBuilder(
- {}, {tab_size: 4, line_length: 50}
- );
+ element.diff = {};
+ element.prefs = {tab_size: 4, line_length: 50};
+ builder = element._getDiffBuilder();
assert.equal(builder._prefs.line_length, 72);
});
});
@@ -237,8 +239,8 @@
});
test('_handlePreferenceError throws with invalid preference', () => {
- const prefs = {tab_size: 0};
- assert.throws(() => element._getDiffBuilder(element.diff, prefs));
+ element.prefs = {tab_size: 0};
+ assert.throws(() => element._getDiffBuilder());
});
test('_handlePreferenceError triggers alert and javascript error', () => {
@@ -696,7 +698,6 @@
suite('rendering text, images and binary files', () => {
let processStub;
let keyLocations;
- let prefs;
let content;
setup(() => {
@@ -705,10 +706,8 @@
processStub = sinon.stub(element.$.processor, 'process')
.returns(Promise.resolve());
keyLocations = {left: {}, right: {}};
- prefs = {
- line_length: 10,
- show_tabs: true,
- tab_size: 4,
+ element.prefs = {
+ ...DEFAULT_PREFS,
context: -1,
syntax_highlighting: true,
};
@@ -725,7 +724,7 @@
test('text', () => {
element.diff = {content};
- return element.render(keyLocations, prefs).then(() => {
+ return element.render(keyLocations).then(() => {
assert.isTrue(processStub.calledOnce);
assert.isFalse(processStub.lastCall.args[1]);
});
@@ -734,7 +733,7 @@
test('image', () => {
element.diff = {content, binary: true};
element.isImageDiff = true;
- return element.render(keyLocations, prefs).then(() => {
+ return element.render(keyLocations).then(() => {
assert.isTrue(processStub.calledOnce);
assert.isTrue(processStub.lastCall.args[1]);
});
@@ -742,7 +741,7 @@
test('binary', () => {
element.diff = {content, binary: true};
- return element.render(keyLocations, prefs).then(() => {
+ return element.render(keyLocations).then(() => {
assert.isTrue(processStub.calledOnce);
assert.isTrue(processStub.lastCall.args[1]);
});
@@ -755,13 +754,7 @@
let keyLocations;
setup(async () => {
- const prefs = {
- line_length: 10,
- show_tabs: true,
- tab_size: 4,
- context: -1,
- syntax_highlighting: true,
- };
+ const prefs = {...DEFAULT_PREFS};
content = [
{
a: ['all work and no play make andybons a dull boy'],
@@ -775,6 +768,7 @@
},
];
element = basicFixture.instantiate();
+ sinon.stub(element, 'dispatchEvent');
outputEl = element.querySelector('#diffTable');
keyLocations = {left: {}, right: {}};
sinon.stub(element, '_getDiffBuilder').callsFake(() => {
@@ -789,53 +783,113 @@
return builder;
});
element.diff = {content};
- await element.render(keyLocations, prefs);
+ element.prefs = prefs;
+ await element.render(keyLocations);
});
- test('addColumns is called', async () => {
- await element.render(keyLocations, {});
+ test('addColumns is called', () => {
assert.isTrue(element._builder.addColumns.called);
});
- test('getSectionsByLineRange one line', () => {
+ test('getGroupsByLineRange one line', () => {
const section = outputEl.querySelector('stub:nth-of-type(3)');
- const sections = element._builder.getSectionsByLineRange(1, 1, 'left');
- assert.equal(sections.length, 1);
- assert.strictEqual(sections[0], section);
+ const groups = element._builder.getGroupsByLineRange(1, 1, 'left');
+ assert.equal(groups.length, 1);
+ assert.strictEqual(groups[0].element, section);
});
- test('getSectionsByLineRange over diff', () => {
+ test('getGroupsByLineRange over diff', () => {
const section = [
outputEl.querySelector('stub:nth-of-type(3)'),
outputEl.querySelector('stub:nth-of-type(4)'),
];
- const sections = element._builder.getSectionsByLineRange(1, 2, 'left');
- assert.equal(sections.length, 2);
- assert.strictEqual(sections[0], section[0]);
- assert.strictEqual(sections[1], section[1]);
+ const groups = element._builder.getGroupsByLineRange(1, 2, 'left');
+ assert.equal(groups.length, 2);
+ assert.strictEqual(groups[0].element, section[0]);
+ assert.strictEqual(groups[1].element, section[1]);
});
test('render-start and render-content are fired', async () => {
- const dispatchEventStub = sinon.stub(element, 'dispatchEvent');
- await element.render(keyLocations, {});
- const firedEventTypes = dispatchEventStub.getCalls()
+ const firedEventTypes = element.dispatchEvent.getCalls()
.map(c => c.args[0].type);
assert.include(firedEventTypes, 'render-start');
assert.include(firedEventTypes, 'render-content');
});
- test('cancel', () => {
+ test('cancel cancels the processor', () => {
const processorCancelStub = sinon.stub(element.$.processor, 'cancel');
element.cancel();
assert.isTrue(processorCancelStub.called);
});
});
+ suite('context hiding and expanding', () => {
+ setup(async () => {
+ element = basicFixture.instantiate();
+ const afterNextRenderPromise = new Promise((resolve, reject) => {
+ afterNextRender(element, resolve);
+ });
+ element.diff = {
+ content: [
+ {ab: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => `unchanged ${i}`)},
+ {a: ['before'], b: ['after']},
+ {ab: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => `unchanged ${10 + i}`)},
+ ],
+ };
+ element.viewMode = DiffViewMode.SIDE_BY_SIDE;
+
+ const keyLocations = {left: {}, right: {}};
+ element.prefs = {
+ ...DEFAULT_PREFS,
+ context: 1,
+ };
+ await element.render(keyLocations);
+ // Make sure all listeners are installed.
+ await afterNextRenderPromise;
+ });
+
+ test('hides lines behind two context controls', () => {
+ const contextControls = element.querySelectorAll('gr-context-controls');
+ assert.equal(contextControls.length, 2);
+
+ const diffRows = element.querySelectorAll('.diff-row');
+ // The first two are LOST and FILE line
+ assert.equal(diffRows.length, 2 + 1 + 1 + 1);
+ assert.include(diffRows[2].textContent, 'unchanged 10');
+ assert.include(diffRows[3].textContent, 'before');
+ assert.include(diffRows[3].textContent, 'after');
+ assert.include(diffRows[4].textContent, 'unchanged 11');
+ });
+
+ test('clicking +x common lines expands those lines', () => {
+ const contextControls = element.querySelectorAll('gr-context-controls');
+ const topExpandCommonButton = contextControls[0].shadowRoot
+ .querySelectorAll('.showContext')[0];
+ assert.include(topExpandCommonButton.textContent, '+9 common lines');
+ topExpandCommonButton.click();
+ const diffRows = element.querySelectorAll('.diff-row');
+ // The first two are LOST and FILE line
+ assert.equal(diffRows.length, 2 + 10 + 1 + 1);
+ assert.include(diffRows[2].textContent, 'unchanged 1');
+ assert.include(diffRows[3].textContent, 'unchanged 2');
+ assert.include(diffRows[4].textContent, 'unchanged 3');
+ assert.include(diffRows[5].textContent, 'unchanged 4');
+ assert.include(diffRows[6].textContent, 'unchanged 5');
+ assert.include(diffRows[7].textContent, 'unchanged 6');
+ assert.include(diffRows[8].textContent, 'unchanged 7');
+ assert.include(diffRows[9].textContent, 'unchanged 8');
+ assert.include(diffRows[10].textContent, 'unchanged 9');
+ assert.include(diffRows[11].textContent, 'unchanged 10');
+ assert.include(diffRows[12].textContent, 'before');
+ assert.include(diffRows[12].textContent, 'after');
+ assert.include(diffRows[13].textContent, 'unchanged 11');
+ });
+ });
+
suite('mock-diff', () => {
let element;
let builder;
let diff;
- let prefs;
let keyLocations;
setup(async () => {
@@ -843,14 +897,14 @@
diff = getMockDiffResponse();
element.diff = diff;
- prefs = {
+ keyLocations = {left: {}, right: {}};
+
+ element.prefs = {
line_length: 80,
show_tabs: true,
tab_size: 4,
};
- keyLocations = {left: {}, right: {}};
-
- await element.render(keyLocations, prefs);
+ await element.render(keyLocations);
builder = element._builder;
});
@@ -988,7 +1042,7 @@
test('_getLineNumberEl unified left', async () => {
// Re-render as unified:
element.viewMode = 'UNIFIED_DIFF';
- await element.render(keyLocations, prefs);
+ await element.render(keyLocations);
builder = element._builder;
const contentEl = builder.getContentByLine(5, 'left',
@@ -1001,7 +1055,7 @@
test('_getLineNumberEl unified right', async () => {
// Re-render as unified:
element.viewMode = 'UNIFIED_DIFF';
- await element.render(keyLocations, prefs);
+ await element.render(keyLocations);
builder = element._builder;
const contentEl = builder.getContentByLine(5, 'right',
@@ -1038,7 +1092,7 @@
test('_getNextContentOnSide unified left', async () => {
// Re-render as unified:
element.viewMode = 'UNIFIED_DIFF';
- await element.render(keyLocations, prefs);
+ await element.render(keyLocations);
builder = element._builder;
const startElem = builder.getContentByLine(5, 'left',
@@ -1055,7 +1109,7 @@
test('_getNextContentOnSide unified right', async () => {
// Re-render as unified:
element.viewMode = 'UNIFIED_DIFF';
- await element.render(keyLocations, prefs);
+ await element.render(keyLocations);
builder = element._builder;
const startElem = builder.getContentByLine(5, 'right',
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index ab2337e..28172e5 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -226,38 +226,22 @@
group.element = element;
}
- getGroupsByLineRange(
+ private getGroupsByLineRange(
startLine: LineNumber,
endLine: LineNumber,
- side?: Side
+ side: Side
) {
- const groups = [];
- for (let i = 0; i < this.groups.length; i++) {
- const group = this.groups[i];
- if (group.lines.length === 0) {
- continue;
- }
- let groupStartLine = 0;
- let groupEndLine = 0;
- if (side) {
- const range = group.lineRange[side];
- groupStartLine = range.start_line;
- groupEndLine = range.end_line;
- }
-
- if (groupStartLine === 0) {
- // Line was removed or added.
- groupStartLine = groupEndLine;
- }
- if (groupEndLine === 0) {
- // Line was removed or added.
- groupEndLine = groupStartLine;
- }
- if (startLine <= groupEndLine && endLine >= groupStartLine) {
- groups.push(group);
- }
- }
- return groups;
+ const startIndex = this.groups.findIndex(group =>
+ group.containsLine(side, startLine)
+ );
+ const endIndex = this.groups.findIndex(group =>
+ group.containsLine(side, endLine)
+ );
+ // The filter preserves the legacy behavior to only return non-context
+ // groups
+ return this.groups
+ .slice(startIndex, endIndex + 1)
+ .filter(group => group.lines.length > 0);
}
getContentTdByLine(
@@ -356,16 +340,6 @@
}
}
- getSectionsByLineRange(
- startLine: LineNumber,
- endLine: LineNumber,
- side: Side
- ) {
- return this.getGroupsByLineRange(startLine, endLine, side).map(
- group => group.element
- );
- }
-
_createContextControls(
section: HTMLElement,
group: GrDiffGroup,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
index 9778bb7..d072145 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
@@ -16,6 +16,7 @@
*/
import {BLANK_LINE, GrDiffLine, GrDiffLineType} from './gr-diff-line';
import {LineRange, Side} from '../../../api/diff';
+import {LineNumber} from './gr-diff-line';
export enum GrDiffGroupType {
/** Unchanged context. */
@@ -409,6 +410,20 @@
return pairs;
}
+ /** Returns true if it is, or contains, a skip group. */
+ hasSkipGroup() {
+ return !!this.skip || this.contextGroups?.some(g => !!g.skip);
+ }
+
+ containsLine(side: Side, line: LineNumber) {
+ if (line === 'FILE' || line === 'LOST') {
+ // For FILE and LOST, beforeNumber and afterNumber are the same
+ return this.lines[0]?.beforeNumber === line;
+ }
+ const lineRange = this.lineRange[side];
+ return lineRange.start_line <= line && line <= lineRange.end_line;
+ }
+
private _updateRangeWithNewLine(line: GrDiffLine) {
if (
line.beforeNumber === 'FILE' ||
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index 0b15933..6f9765d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -84,7 +84,6 @@
import {assertIsDefined} from '../../../utils/common-util';
import {debounce, DelayedTask} from '../../../utils/async-util';
import {
- DiffContextExpandedEventDetail,
getResponsiveMode,
isResponsive,
} from '../gr-diff-builder/gr-diff-builder';
@@ -543,11 +542,6 @@
return classes.join(' ');
}
- _handleDiffContextExpanded(e: CustomEvent<DiffContextExpandedEventDetail>) {
- // Don't stop propagation. The host may listen for reporting or resizing.
- this.$.diffBuilder.rerenderSection(e.detail.groups, e.detail.section);
- }
-
_handleTap(e: CustomEvent) {
const el = (dom(e) as EventApi).localTarget as Element;
@@ -858,18 +852,17 @@
this._showWarning = false;
const keyLocations = this._computeKeyLocations();
- const bypassPrefs = this._getBypassPrefs(this.prefs);
- this.$.diffBuilder
- .render(keyLocations, bypassPrefs, this.renderPrefs)
- .then(() => {
- this.dispatchEvent(
- new CustomEvent('render', {
- bubbles: true,
- composed: true,
- detail: {contentRendered: true},
- })
- );
- });
+ this.$.diffBuilder.prefs = this._getBypassPrefs(this.prefs);
+ this.$.diffBuilder.renderPrefs = this.renderPrefs;
+ this.$.diffBuilder.render(keyLocations).then(() => {
+ this.dispatchEvent(
+ new CustomEvent('render', {
+ bubbles: true,
+ composed: true,
+ detail: {contentRendered: true},
+ })
+ );
+ });
}
_handleRenderContent() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
index 67b7a9f..d288008 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
@@ -570,7 +570,6 @@
<div
class$="[[_computeContainerClass(loggedIn, viewMode, displayLine)]]"
on-click="_handleTap"
- on-diff-context-expanded="_handleDiffContextExpanded"
>
<gr-diff-selection diff="[[diff]]">
<gr-diff-highlight
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
index 14f6f13..ef3ca39 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
@@ -183,9 +183,9 @@
element.changeNum = 123;
element.patchRange = {basePatchNum: 1, patchNum: 2};
element.path = 'file.txt';
-
- element.$.diffBuilder._builder = element.$.diffBuilder._getDiffBuilder(
- getMockDiffResponse(), {...MINIMAL_PREFS});
+ element.$.diffBuilder.diff = getMockDiffResponse();
+ element.$.diffBuilder.prefs = {...MINIMAL_PREFS};
+ element.$.diffBuilder._builder = element.$.diffBuilder._getDiffBuilder();
// No thread groups.
assert.isNotOk(element._getThreadGroupForLine(contentEl));
@@ -497,21 +497,6 @@
await promise;
});
- test('_handleTap context', async () => {
- const rerenderSectionStub =
- sinon.stub(element.$.diffBuilder, 'rerenderSection');
- const el = document.createElement('div');
- el.className = 'showContext';
- const promise = mockPromise();
- el.addEventListener('click', e => {
- element._handleDiffContextExpanded(e);
- assert.isTrue(rerenderSectionStub.called);
- promise.resolve();
- });
- el.click();
- await promise;
- });
-
test('_handleTap content', async () => {
const content = document.createElement('div');
const lineEl = document.createElement('div');
@@ -859,7 +844,7 @@
assert.equal(element.prefs.context, 3);
assert.equal(element._safetyBypass, -1);
- assert.equal(renderStub.firstCall.args[1].context, -1);
+ assert.equal(element.$.diffBuilder.prefs.context, -1);
});
test('toggles collapse context from bypass', async () => {
@@ -872,7 +857,7 @@
assert.equal(element.prefs.context, 3);
assert.isNull(element._safetyBypass);
- assert.equal(renderStub.firstCall.args[1].context, 3);
+ assert.equal(element.$.diffBuilder.prefs.context, 3);
});
test('toggles collapse context from pref using default', async () => {
@@ -884,7 +869,7 @@
assert.equal(element.prefs.context, -1);
assert.equal(element._safetyBypass, 10);
- assert.equal(renderStub.firstCall.args[1].context, 10);
+ assert.equal(element.$.diffBuilder.prefs.context, 10);
});
});
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index e302c8f..288489e 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -65,7 +65,6 @@
import {
AppElementJustRegisteredParams,
AppElementParams,
- AppElementSearchParam,
isAppElementJustRegisteredParams,
} from './gr-app-types';
import {GrMainHeader} from './core/gr-main-header/gr-main-header';
@@ -79,7 +78,7 @@
TitleChangeEventDetail,
ValueChangedEvent,
} from '../types/events';
-import {ViewState} from '../types/types';
+import {ChangeListViewState, ViewState} from '../types/types';
import {GerritView} from '../services/router/router-model';
import {LifeCycle} from '../constants/reporting';
import {fireIronAnnounce} from '../utils/event-util';
@@ -621,9 +620,12 @@
: 'app-theme-light';
}
- _handleSearchQueryChanged(e: ValueChangedEvent) {
- if (!this.params) return;
- (this.params as AppElementSearchParam).query = e.detail.value;
+ _handleViewStateChanged(e: ValueChangedEvent<ChangeListViewState>) {
+ if (!this._viewState) return;
+ this._viewState.changeListView = {
+ ...this._viewState.changeListView,
+ ...e.detail.value,
+ };
}
}
diff --git a/polygerrit-ui/app/elements/gr-app-element_html.ts b/polygerrit-ui/app/elements/gr-app-element_html.ts
index a6afb33..fcb3435 100644
--- a/polygerrit-ui/app/elements/gr-app-element_html.ts
+++ b/polygerrit-ui/app/elements/gr-app-element_html.ts
@@ -100,7 +100,6 @@
<gr-main-header
id="mainHeader"
search-query="[[params.query]]"
- on-search-query-changed="_handleSearchQueryChanged"
on-mobile-search="_mobileSearchToggle"
on-show-keyboard-shortcuts="handleShowKeyboardShortcuts"
mobile-search-hidden="[[!mobileSearch]]"
@@ -116,7 +115,6 @@
search-query="[[params.query]]"
server-config="[[_serverConfig]]"
hidden="[[!mobileSearch]]"
- on-search-query-changed="_handleSearchQueryChanged"
>
</gr-smart-search>
</template>
@@ -124,7 +122,8 @@
<gr-change-list-view
params="[[params]]"
account="[[_account]]"
- view-state="{{_viewState.changeListView}}"
+ view-state="[[_viewState.changeListView]]"
+ on-view-state-changed="_handleViewStateChanged"
></gr-change-list-view>
</template>
<template is="dom-if" if="[[_showDashboardView]]" restamp="true">
diff --git a/tools/deps.bzl b/tools/deps.bzl
index f64efff..a202405 100644
--- a/tools/deps.bzl
+++ b/tools/deps.bzl
@@ -82,12 +82,6 @@
)
maven_jar(
- name = "error-prone-annotations",
- artifact = "com.google.errorprone:error_prone_annotations:2.3.3",
- sha1 = "42aa5155a54a87d70af32d4b0d06bf43779de0e2",
- )
-
- maven_jar(
name = "gson",
artifact = "com.google.code.gson:gson:2.8.7",
sha1 = "69d9503ea0a40ee16f0bcdac7e3eaf83d0fa914a",
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index 80db2fa..e567668 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -123,6 +123,12 @@
# Google internal dependencies: these are developed at Google, so there is
# no concern about version skew.
+ maven_jar(
+ name = "error-prone-annotations",
+ artifact = "com.google.errorprone:error_prone_annotations:2.3.3",
+ sha1 = "42aa5155a54a87d70af32d4b0d06bf43779de0e2",
+ )
+
FLOGGER_VERS = "0.7.4"
maven_jar(