Merge "Add slots for post-line content projection"
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseChain.java b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
index db94db2..34a2623 100644
--- a/java/com/google/gerrit/server/restapi/change/RebaseChain.java
+++ b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
@@ -245,6 +245,9 @@
try (Repository repo = repoManager.openRepository(tipRsrc.getProject());
RevWalk rw = new RevWalk(repo)) {
List<PatchSetData> chain = getChainForCurrentPatchSet(tipRsrc);
+ if (chain.size() <= 1) {
+ return description;
+ }
PatchSetData oldestAncestor = chain.get(0);
if (rebaseUtil.canRebase(
oldestAncestor.patchSet(), oldestAncestor.data().change().getDest(), repo, rw)) {
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteRef.java b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
index 5a84f69..b7fe46e 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteRef.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
@@ -16,6 +16,7 @@
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.gerrit.entities.RefNames.isConfigRef;
+import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.BRANCH_MODIFICATION;
import static java.lang.String.format;
import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
@@ -41,6 +42,7 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefValidationHelper;
import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -102,56 +104,58 @@
*/
public void deleteSingleRef(ProjectState projectState, String ref, @Nullable String prefix)
throws IOException, ResourceConflictException, AuthException, PermissionBackendException {
- if (prefix != null && !ref.startsWith(R_REFS)) {
- ref = prefix + ref;
- }
+ try (RefUpdateContext ctx = RefUpdateContext.open(BRANCH_MODIFICATION)) {
+ if (prefix != null && !ref.startsWith(R_REFS)) {
+ ref = prefix + ref;
+ }
- projectState.checkStatePermitsWrite();
- permissionBackend
- .currentUser()
- .project(projectState.getNameKey())
- .ref(ref)
- .check(RefPermission.DELETE);
+ projectState.checkStatePermitsWrite();
+ permissionBackend
+ .currentUser()
+ .project(projectState.getNameKey())
+ .ref(ref)
+ .check(RefPermission.DELETE);
- try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
- RefUpdate.Result result;
- RefUpdate u = repository.updateRef(ref);
- u.setExpectedOldObjectId(repository.exactRef(ref).getObjectId());
- u.setNewObjectId(ObjectId.zeroId());
- u.setForceUpdate(true);
- refDeletionValidator.validateRefOperation(
- projectState.getName(),
- identifiedUser.get(),
- u,
- /* pushOptions */ ImmutableListMultimap.of());
- result = u.delete();
+ try (Repository repository = repoManager.openRepository(projectState.getNameKey())) {
+ RefUpdate.Result result;
+ RefUpdate u = repository.updateRef(ref);
+ u.setExpectedOldObjectId(repository.exactRef(ref).getObjectId());
+ u.setNewObjectId(ObjectId.zeroId());
+ u.setForceUpdate(true);
+ refDeletionValidator.validateRefOperation(
+ projectState.getName(),
+ identifiedUser.get(),
+ u,
+ /* pushOptions */ ImmutableListMultimap.of());
+ result = u.delete();
- switch (result) {
- case NEW:
- case NO_CHANGE:
- case FAST_FORWARD:
- case FORCED:
- referenceUpdated.fire(
- projectState.getNameKey(),
- u,
- ReceiveCommand.Type.DELETE,
- identifiedUser.get().state());
- break;
+ switch (result) {
+ case NEW:
+ case NO_CHANGE:
+ case FAST_FORWARD:
+ case FORCED:
+ referenceUpdated.fire(
+ projectState.getNameKey(),
+ u,
+ ReceiveCommand.Type.DELETE,
+ identifiedUser.get().state());
+ break;
- case REJECTED_CURRENT_BRANCH:
- logger.atFine().log("Cannot delete current branch %s: %s", ref, result.name());
- throw new ResourceConflictException("cannot delete current branch");
+ case REJECTED_CURRENT_BRANCH:
+ logger.atFine().log("Cannot delete current branch %s: %s", ref, result.name());
+ throw new ResourceConflictException("cannot delete current branch");
- case LOCK_FAILURE:
- throw new LockFailureException(String.format("Cannot delete %s", ref), u);
- case IO_FAILURE:
- case NOT_ATTEMPTED:
- case REJECTED:
- case RENAMED:
- case REJECTED_MISSING_OBJECT:
- case REJECTED_OTHER_REASON:
- default:
- throw new StorageException(String.format("Cannot delete %s: %s", ref, result.name()));
+ case LOCK_FAILURE:
+ throw new LockFailureException(String.format("Cannot delete %s", ref), u);
+ case IO_FAILURE:
+ case NOT_ATTEMPTED:
+ case REJECTED:
+ case RENAMED:
+ case REJECTED_MISSING_OBJECT:
+ case REJECTED_OTHER_REASON:
+ default:
+ throw new StorageException(String.format("Cannot delete %s: %s", ref, result.name()));
+ }
}
}
}
diff --git a/java/com/google/gerrit/server/update/context/RefUpdateContext.java b/java/com/google/gerrit/server/update/context/RefUpdateContext.java
index d1c5ff8..3ac0f0d 100644
--- a/java/com/google/gerrit/server/update/context/RefUpdateContext.java
+++ b/java/com/google/gerrit/server/update/context/RefUpdateContext.java
@@ -88,7 +88,7 @@
ACCOUNTS_UPDATE,
/** A ref is updated as a part of direct push. */
DIRECT_PUSH,
- /** A ref is updated as a part of explicit branch update operation. */
+ /** A ref is updated as a part of explicit branch or ref update operation. */
BRANCH_MODIFICATION,
/** A ref is updated as a part of explicit tag update operation. */
TAG_MODIFICATION,
@@ -106,7 +106,13 @@
/** A ref is updated as a part of versioned meta data change. */
VERSIONED_META_DATA_CHANGE,
/** A ref is updated as a part of commit-ban operation. */
- BAN_COMMIT
+ BAN_COMMIT,
+ /**
+ * A ref is updated inside a plugin.
+ *
+ * <p>If a plugin updates one of a special refs - it must also open a nested context.
+ */
+ PLUGIN,
}
/** Opens a provided context. */
diff --git a/java/com/google/gerrit/testing/InMemoryRepositoryManager.java b/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
index 4fe6ff7..650c218 100644
--- a/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
+++ b/java/com/google/gerrit/testing/InMemoryRepositoryManager.java
@@ -26,6 +26,7 @@
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.INIT_REPO;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.MERGE_CHANGE;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.OFFLINE_OPERATION;
+import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.PLUGIN;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.REPO_SEQ;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.TAG_MODIFICATION;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.VERSIONED_META_DATA_CHANGE;
@@ -160,6 +161,8 @@
RefUpdateContext.hasOpen(MERGE_CHANGE)
|| RefUpdateContext.hasOpen(RefUpdateType.BRANCH_MODIFICATION)
|| RefUpdateContext.hasOpen(RefUpdateType.UPDATE_SUPERPROJECT)
+ // Plugin can update any ref
+ || RefUpdateContext.hasOpen(PLUGIN)
|| isTestRepoCall(),
"Ordinary ref '%s' is updated outside of the expected operation. Wrap code in the correct RefUpdateContext or add the ref as a special ref.",
refName);
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index 342b876..19207bc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -42,6 +42,7 @@
import {classMap} from 'lit/directives/class-map.js';
import {createSearchUrl} from '../../../models/views/search';
import {createChangeUrl} from '../../../models/views/change';
+import {userModelToken} from '../../../models/user/user-model';
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
enum ChangeSize {
@@ -94,9 +95,6 @@
sectionName?: string;
@property({type: Boolean})
- showStar = false;
-
- @property({type: Boolean})
showNumber = false;
@property({type: String})
@@ -125,6 +123,10 @@
private readonly getNavigation = resolve(this, navigationToken);
+ private readonly getUserModel = resolve(this, userModelToken);
+
+ @state() private isLoggedIn = false;
+
constructor() {
super();
subscribe(
@@ -134,6 +136,11 @@
this.updateCheckedState(selectedChangeNums);
}
);
+ subscribe(
+ this,
+ () => this.getUserModel().loggedIn$,
+ isLoggedIn => (this.isLoggedIn = isLoggedIn)
+ );
}
override connectedCallback() {
@@ -332,6 +339,8 @@
}
private renderCellSelectionBox() {
+ if (!this.isLoggedIn) return;
+
return html`
<td class="cell selection">
<!--
@@ -352,7 +361,7 @@
}
private renderCellStar() {
- if (!this.showStar) return;
+ if (!this.isLoggedIn) return;
return html`
<td class="cell star">
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
index c7cb5b8..5e31cc8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
@@ -8,9 +8,11 @@
import {
SubmitRequirementResultInfo,
NumericChangeId,
+ Timestamp,
} from '../../../api/rest-api';
import '../../../test/common-test-setup';
import {
+ createAccountWithEmail,
createAccountWithId,
createChange,
createSubmitRequirementExpressionInfo,
@@ -21,7 +23,6 @@
import {
query,
queryAndAssert,
- stubRestApi,
waitUntilObserved,
} from '../../../test/test-utils';
import {
@@ -43,6 +44,7 @@
bulkActionsModelToken,
BulkActionsModel,
} from '../../../models/bulk-actions/bulk-actions-model';
+import {UserModel, userModelToken} from '../../../models/user/user-model';
import {createTestAppContext} from '../../../test/test-app-context-init';
import {ColumnNames} from '../../../constants/constants';
import {testResolver} from '../../../test/common-test-setup';
@@ -58,13 +60,13 @@
let element: GrChangeListItem;
let bulkActionsModel: BulkActionsModel;
+ let userModel: UserModel;
setup(async () => {
- stubRestApi('getLoggedIn').returns(Promise.resolve(false));
-
bulkActionsModel = new BulkActionsModel(
createTestAppContext().restApiService
);
+ userModel = testResolver(userModelToken);
element = (
await fixture<DIProviderElement>(
wrapInProvider(
@@ -105,6 +107,10 @@
test('bulk actions checkboxes', async () => {
element.change = {...createChange(), _number: 1 as NumericChangeId};
bulkActionsModel.sync([element.change]);
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
const checkbox = queryAndAssert<HTMLInputElement>(
@@ -134,6 +140,10 @@
element.globalIndex = 5;
element.change = {...createChange(), _number: 1 as NumericChangeId};
bulkActionsModel.sync([element.change]);
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
const checkbox = queryAndAssert<HTMLInputElement>(
@@ -147,6 +157,10 @@
});
test('checkbox state updates with model updates', async () => {
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
element.requestUpdate();
await element.updateComplete;
@@ -168,6 +182,10 @@
});
test('checkbox state updates with change id update', async () => {
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
element.requestUpdate();
await element.updateComplete;
@@ -361,7 +379,10 @@
const change = createChange();
bulkActionsModel.sync([change]);
bulkActionsModel.addSelectedChangeNum(change._number);
- element.showStar = true;
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
element.showNumber = true;
element.account = createAccountWithId(1);
element.config = createServerInfo();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts
index 8227e11..61b276e 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts
@@ -15,14 +15,15 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {Metadata} from '../../../utils/change-metadata-util';
import {WAITING} from '../../../constants/constants';
-import {provide} from '../../../models/dependency';
+import {provide, resolve} from '../../../models/dependency';
import {
bulkActionsModelToken,
BulkActionsModel,
} from '../../../models/bulk-actions/bulk-actions-model';
+import {createSearchUrl} from '../../../models/views/search';
+import {userModelToken} from '../../../models/user/user-model';
import {subscribe} from '../../lit/subscription-controller';
import {classMap} from 'lit/directives/class-map.js';
-import {createSearchUrl} from '../../../models/views/search';
const NUMBER_FIXED_COLUMNS = 4;
const LABEL_PREFIX_INVALID_PROLOG = 'Invalid-Prolog-Rules-Label-Name--';
@@ -52,9 +53,6 @@
visibleChangeTableColumns?: string[];
@property({type: Boolean})
- showStar = false;
-
- @property({type: Boolean})
showNumber?: boolean; // No default value to prevent flickering.
@property({type: Number})
@@ -104,6 +102,10 @@
getAppContext().restApiService
);
+ private readonly getUserModel = resolve(this, userModelToken);
+
+ private isLoggedIn = false;
+
static override get styles() {
return [
changeListStyles,
@@ -156,6 +158,11 @@
() => this.bulkActionsModel.totalChangeCount$,
totalChangeCount => (this.totalChangeCount = totalChangeCount)
);
+ subscribe(
+ this,
+ () => this.getUserModel().loggedIn$,
+ isLoggedIn => (this.isLoggedIn = isLoggedIn)
+ );
}
override willUpdate(changedProperties: PropertyValues) {
@@ -189,8 +196,8 @@
<td class="leftPadding" aria-hidden="true"></td>
<td
class="star"
- ?aria-hidden=${!this.showStar}
- ?hidden=${!this.showStar}
+ ?aria-hidden=${!this.isLoggedIn}
+ ?hidden=${!this.isLoggedIn}
></td>
<td class="cell" colspan=${colSpan}>
${this.changeSection.emptyStateSlotName
@@ -213,7 +220,7 @@
<tbody>
<tr class="groupHeader">
<td aria-hidden="true" class="leftPadding"></td>
- <td aria-hidden="true" class="star" ?hidden=${!this.showStar}></td>
+ <td aria-hidden="true" class="star" ?hidden=${!this.isLoggedIn}></td>
<td class="cell" colspan=${colSpan}>
<h2 class="heading-3">
<a
@@ -248,7 +255,7 @@
: html` <td
class="star"
aria-label="Star status column"
- ?hidden=${!this.showStar}
+ ?hidden=${!this.isLoggedIn}
></td>
<td class="number" ?hidden=${!this.showNumber}>#</td>
${columns.map(item => this.renderHeaderCell(item))}
@@ -267,7 +274,7 @@
const indeterminate =
this.numSelected > 0 && this.numSelected !== this.totalChangeCount;
return html`
- <td class="selection">
+ <td class="selection" ?hidden=${!this.isLoggedIn}>
<!--
The .checked property must be used rather than the attribute because
the attribute only controls the default checked state and does not
@@ -322,7 +329,6 @@
.sectionName=${this.changeSection.name}
.visibleChangeTableColumns=${columns}
.showNumber=${this.showNumber}
- ?showStar=${this.showStar}
.usp=${this.usp}
.labelNames=${this.labelNames}
.globalIndex=${this.startIndex + index}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section_test.ts
index 8dfecdc..63552c7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section_test.ts
@@ -13,9 +13,10 @@
import {
createChange,
createAccountDetailWithId,
+ createAccountWithEmail,
createServerInfo,
} from '../../../test/test-data-generators';
-import {NumericChangeId, ChangeInfoId} from '../../../api/rest-api';
+import {ChangeInfoId, NumericChangeId, Timestamp} from '../../../api/rest-api';
import {
queryAll,
query,
@@ -27,11 +28,15 @@
import {ChangeListSection} from '../gr-change-list/gr-change-list';
import {fixture, html, assert} from '@open-wc/testing';
import {ColumnNames} from '../../../constants/constants';
+import {testResolver} from '../../../test/common-test-setup';
+import {UserModel, userModelToken} from '../../../models/user/user-model';
suite('gr-change-list section', () => {
let element: GrChangeListSection;
+ let userModel: UserModel;
setup(async () => {
+ userModel = testResolver(userModelToken);
const changeSection: ChangeListSection = {
name: 'test',
query: 'test',
@@ -193,6 +198,10 @@
],
emptyStateSlotName: 'test',
};
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
let rows = queryAll(element, 'gr-change-list-item');
assert.lengthOf(rows, 2);
@@ -235,6 +244,10 @@
],
emptyStateSlotName: 'test',
};
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
const rows = queryAll(element, 'gr-change-list-item');
@@ -273,6 +286,31 @@
});
});
+ test('no checkbox when logged out', async () => {
+ element.changeSection = {
+ name: 'test',
+ query: 'test',
+ results: [
+ {
+ ...createChange(),
+ _number: 1 as NumericChangeId,
+ id: '1' as ChangeInfoId,
+ },
+ {
+ ...createChange(),
+ _number: 2 as NumericChangeId,
+ id: '2' as ChangeInfoId,
+ },
+ ],
+ emptyStateSlotName: 'test',
+ };
+ userModel.setAccount(undefined);
+ await element.updateComplete;
+ const rows = queryAll(element, 'gr-change-list-item');
+ assert.lengthOf(rows, 2);
+ assert.isUndefined(query<HTMLInputElement>(rows[0], 'input'));
+ });
+
test('colspans', async () => {
element.visibleChangeTableColumns = [];
element.changeSection = {results: [{...createChange()}]};
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 d2ba2c9..1c86354 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
@@ -195,7 +195,6 @@
.account=${this.account}
.changes=${this.changes}
.preferences=${this.preferences}
- .showStar=${this.loggedIn}
@toggle-star=${(e: CustomEvent<ChangeStarToggleStarDetail>) => {
this.handleToggleStar(e);
}}
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 4c43da5..748c2b8 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
@@ -113,9 +113,6 @@
showNumber?: boolean; // No default value to prevent flickering.
@property({type: Boolean})
- showStar = false;
-
- @property({type: Boolean})
showReviewedState = false;
@property({type: Array})
@@ -270,7 +267,6 @@
sectionIndex,
this.sections
)}
- ?showStar=${this.showStar}
.showNumber=${this.showNumber}
.visibleChangeTableColumns=${this.visibleChangeTableColumns}
.usp=${this.usp}
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 bef3166..7e9735f 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
@@ -23,6 +23,7 @@
} from '../../../constants/constants';
import {AccountId, NumericChangeId} from '../../../types/common';
import {
+ createAccountWithEmail,
createChange,
createServerInfo,
createSubmitRequirementResultInfo,
@@ -32,12 +33,16 @@
import {fixture, assert} from '@open-wc/testing';
import {html} from 'lit';
import {testResolver} from '../../../test/common-test-setup';
+import {Timestamp} from '../../../api/rest-api';
+import {UserModel, userModelToken} from '../../../models/user/user-model';
suite('gr-change-list basic tests', () => {
let element: GrChangeList;
+ let userModel: UserModel;
setup(async () => {
element = await fixture(html`<gr-change-list></gr-change-list>`);
+ userModel = testResolver(userModelToken);
});
test('renders', async () => {
@@ -285,6 +290,12 @@
});
test('toggle checkbox keyboard shortcut', async () => {
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
+ await element.updateComplete;
+
const getCheckbox = (item: GrChangeListItem) =>
queryAndAssert<HTMLInputElement>(query(item, '.selection'), 'input');
@@ -515,7 +526,7 @@
assert.isTrue(element.isColumnEnabled('Subject'));
});
- test('showStar and showNumber', async () => {
+ test('loggedIn and showNumber', async () => {
element.sections = [{results: [{...createChange()}], name: 'a'}];
element.account = {_account_id: 1001 as AccountId};
element.preferences = {
@@ -534,6 +545,7 @@
],
};
element.config = createServerInfo();
+ userModel.setAccount(undefined);
await element.updateComplete;
const section = query<GrChangeListSection>(
element,
@@ -547,7 +559,10 @@
assert.isNotOk(query(query(section, 'gr-change-list-item'), '.star'));
assert.isNotOk(query(query(section, 'gr-change-list-item'), '.number'));
- element.showStar = true;
+ userModel.setAccount({
+ ...createAccountWithEmail('abc@def.com'),
+ registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+ });
await element.updateComplete;
await section.updateComplete;
assert.isOk(query(query(section, 'gr-change-list-item'), '.star'));
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 d013654..cd30440 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
@@ -284,7 +284,6 @@
${this.renderUserHeader()}
<h1 class="assistive-tech-only">Dashboard</h1>
<gr-change-list
- ?showStar=${true}
.account=${this.account}
.preferences=${this.preferences}
.sections=${this.results}
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
index 17d7e95..84a3139 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
@@ -82,7 +82,7 @@
<div class="loading" hidden="">Loading...</div>
<div>
<h1 class="assistive-tech-only">Dashboard</h1>
- <gr-change-list showstar="">
+ <gr-change-list>
<div id="emptyOutgoing" slot="outgoing-slot">No changes</div>
<div id="emptyYourTurn" slot="your-turn-slot">
<span> No changes need your attention  ðŸŽ‰ </span>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index 6f8bd9a..c2739f3 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -218,7 +218,6 @@
<gr-autocomplete
id="parentInput"
.query=${this.query}
- no-debounce
.text=${this.text}
@text-changed=${(e: ValueChangedEvent) =>
(this.text = e.detail.value)}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
index 776e923..2644d81 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
@@ -74,7 +74,6 @@
<gr-autocomplete
allow-non-suggested-values=""
id="parentInput"
- no-debounce=""
placeholder="Change number, ref, or commit hash"
>
</gr-autocomplete>
@@ -305,7 +304,6 @@
test('input text change triggers function', async () => {
const recentChangesSpy = sinon.spy(element, 'getRecentChanges');
- element.parentInput.noDebounce = true;
pressKey(
queryAndAssert(queryAndAssert(element, '#parentInput'), '#input'),
Key.ENTER
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index fbc87ed..314e126 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -278,7 +278,7 @@
// fallback to gerrit's official doc
let baseUrl =
this.docsBaseUrl ||
- 'https://gerrit-review.googlesource.com/documentation/';
+ 'https://gerrit-review.googlesource.com/Documentation/';
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
}
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
index 43b1f86..0694453 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
@@ -319,7 +319,7 @@
await element.updateComplete;
assert.equal(
element.computeHelpDocLink(),
- 'https://gerrit-review.googlesource.com/documentation/' +
+ 'https://gerrit-review.googlesource.com/Documentation/' +
'user-search.html'
);
});
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
index 31283ad..0729f21 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
@@ -217,16 +217,20 @@
assert.isFalse(hideDialogStub.called);
queryAndAssert<GrButton>(element, '#open').click();
element.patchNum = 1 as RevisionPatchSetNum;
- await waitUntilVisible(element.modal!);
+ await showDialogSpy.lastCall.returnValue;
assert.isTrue(hideDialogStub.called);
assert.isTrue(element.openDialog!.disabled);
assert.isFalse(queryStub.called);
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
openAutoComplete.focused = true;
- openAutoComplete.noDebounce = true;
openAutoComplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ openAutoComplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await openAutoComplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
await waitUntil(() => !element.openDialog!.disabled);
queryAndAssert<GrButton>(
@@ -242,7 +246,6 @@
queryAndAssert<GrButton>(element, '#open').click();
await waitUntilVisible(element.modal!);
assert.isTrue(element.openDialog!.disabled);
- openAutoComplete.noDebounce = true;
openAutoComplete.text = 'src/test.cpp';
await element.updateComplete;
await waitUntil(() => !element.openDialog!.disabled);
@@ -277,9 +280,13 @@
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
deleteAutocomplete.focused = true;
- deleteAutocomplete.noDebounce = true;
deleteAutocomplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ deleteAutocomplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await deleteAutocomplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
await waitUntil(() => !element.deleteDialog!.disabled);
queryAndAssert<GrButton>(
@@ -304,9 +311,13 @@
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
deleteAutocomplete.focused = true;
- deleteAutocomplete.noDebounce = true;
deleteAutocomplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ deleteAutocomplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await deleteAutocomplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
await waitUntil(() => !element.deleteDialog!.disabled);
queryAndAssert<GrButton>(
@@ -363,9 +374,13 @@
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
renameAutocomplete.focused = true;
- renameAutocomplete.noDebounce = true;
renameAutocomplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ renameAutocomplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await renameAutocomplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
assert.isTrue(element.renameDialog!.disabled);
@@ -395,9 +410,13 @@
// Setup focused manually - in headless mode Chrome sometimes doesn't
// setup focus. waitEventLoop() doesn't help.
renameAutocomplete.focused = true;
- renameAutocomplete.noDebounce = true;
renameAutocomplete.text = 'src/test.cpp';
+ // Focus happens after updateComplete, so we first wait for it explicitly.
+ await new Promise<void>(resolve => {
+ renameAutocomplete.addEventListener('focus', () => resolve());
+ });
await element.updateComplete;
+ await renameAutocomplete.latestSuggestionUpdateComplete;
assert.isTrue(queryStub.called);
assert.isTrue(element.renameDialog!.disabled);
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
index 6b4d670..cc2723e 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
@@ -401,8 +401,8 @@
);
input.text = 'newTest';
input.input!.focus();
- input.noDebounce = true;
await element.updateComplete;
+ await input.latestSuggestionUpdateComplete;
assert.isTrue(getSuggestionsStub.calledOnce);
assert.equal(getSuggestionsStub.lastCall.args[0], 'newTest');
await waitUntil(() => makeSuggestionItemSpy.getCalls().length === 2);
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index de97741..ab74e8b 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -9,7 +9,11 @@
import '../../../styles/shared-styles';
import {GrAutocompleteDropdown} from '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
import {fire, fireEvent} from '../../../utils/event-util';
-import {debounce, DelayedTask} from '../../../utils/async-util';
+import {
+ debounce,
+ DelayedTask,
+ ResolvedDelayedTaskStatus,
+} from '../../../utils/async-util';
import {PropertyType} from '../../../types/common';
import {modifierPressed} from '../../../utils/dom-util';
import {sharedStyles} from '../../../styles/shared-styles';
@@ -150,12 +154,6 @@
@property({type: Boolean, attribute: 'warn-uncommitted'})
warnUncommitted = false;
- /**
- * When true, querying for suggestions is not debounced w/r/t keypresses
- */
- @property({type: Boolean, attribute: 'no-debounce'})
- noDebounce = false;
-
@property({type: Boolean, attribute: 'show-blue-focus-border'})
showBlueFocusBorder = false;
@@ -183,6 +181,15 @@
private updateSuggestionsTask?: DelayedTask;
+ /**
+ * @return Promise that resolves when suggestions are update.
+ */
+ get latestSuggestionUpdateComplete():
+ | Promise<ResolvedDelayedTaskStatus>
+ | undefined {
+ return this.updateSuggestionsTask?.promise;
+ }
+
get nativeInput() {
return (this.input!.inputElement as IronInputElement)
.inputElement as HTMLInputElement;
@@ -254,11 +261,7 @@
}
override willUpdate(changedProperties: PropertyValues) {
- if (
- changedProperties.has('text') ||
- changedProperties.has('threshold') ||
- changedProperties.has('noDebounce')
- ) {
+ if (changedProperties.has('text') || changedProperties.has('threshold')) {
this.updateSuggestions();
}
if (
@@ -404,12 +407,7 @@
}
updateSuggestions() {
- if (
- this.text === undefined ||
- this.threshold === undefined ||
- this.noDebounce === undefined
- )
- return;
+ if (this.text === undefined || this.threshold === undefined) return;
// Reset suggestions for every update
// This will also prevent from carrying over suggestions:
@@ -462,15 +460,11 @@
});
};
- if (this.noDebounce) {
- update();
- } else {
- this.updateSuggestionsTask = debounce(
- this.updateSuggestionsTask,
- update,
- DEBOUNCE_WAIT_MS
- );
- }
+ this.updateSuggestionsTask = debounce(
+ this.updateSuggestionsTask,
+ update,
+ DEBOUNCE_WAIT_MS
+ );
}
setFocus(focused: boolean) {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
index eb1efec..81949c7 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.ts
@@ -391,7 +391,6 @@
element.query = queryStub;
await element.updateComplete;
- element.noDebounce = false;
focusOnInput();
element.text = 'a';
@@ -413,7 +412,6 @@
test('empty text results in no suggestions', async () => {
element.text = '';
element.threshold = 0;
- element.noDebounce = false;
await element.updateComplete;
assert.equal(element.suggestions.length, 0);
});
@@ -475,7 +473,6 @@
assert.equal(element.suggestions.length, 1);
element.text = '';
element.threshold = 0;
- element.noDebounce = false;
await element.updateComplete;
assert.equal(element.suggestions.length, 0);
});
@@ -494,7 +491,6 @@
await waitUntil(() => element.queryErrorMessage === 'Test error');
element.text = '';
element.threshold = 0;
- element.noDebounce = false;
await element.updateComplete;
assert.isUndefined(element.queryErrorMessage);
});
diff --git a/polygerrit-ui/app/utils/async-util.ts b/polygerrit-ui/app/utils/async-util.ts
index cae6319..752de62 100644
--- a/polygerrit-ui/app/utils/async-util.ts
+++ b/polygerrit-ui/app/utils/async-util.ts
@@ -37,6 +37,11 @@
export const _testOnly_allTasks = new Map<number, DelayedTask>();
+export enum ResolvedDelayedTaskStatus {
+ CALLBACK_EXECUTED = 'CALLBACK_EXECUTED',
+ TASK_CANCELLED = 'TASK_CANCELLED',
+}
+
/**
* This is just a very simple and small wrapper around setTimeout(). Instead of
* the usual:
@@ -52,34 +57,54 @@
* It is just nicer to have an object for this instead of a number as a handle.
*/
export class DelayedTask {
- private timer?: number;
+ private timerId?: number;
+
+ /**
+ * Promise that is resolved after the callback is run or the task is
+ * cancelled.
+ */
+ public readonly promise: Promise<ResolvedDelayedTaskStatus>;
+
+ private resolvePromise?: (
+ value: ResolvedDelayedTaskStatus | PromiseLike<ResolvedDelayedTaskStatus>
+ ) => void;
constructor(private callback: () => void, waitMs = 0) {
- this.timer = window.setTimeout(() => {
- if (this.timer) _testOnly_allTasks.delete(this.timer);
- this.timer = undefined;
- if (this.callback) this.callback();
- }, waitMs);
- _testOnly_allTasks.set(this.timer, this);
+ this.promise = new Promise(resolve => {
+ this.resolvePromise = resolve;
+ this.timerId = window.setTimeout(() => {
+ if (this.timerId) _testOnly_allTasks.delete(this.timerId);
+ this.timerId = undefined;
+ if (this.callback) this.callback();
+ resolve(ResolvedDelayedTaskStatus.CALLBACK_EXECUTED);
+ }, waitMs);
+ _testOnly_allTasks.set(this.timerId, this);
+ });
+ }
+
+ private cancelTimer() {
+ window.clearTimeout(this.timerId);
+ if (this.timerId) _testOnly_allTasks.delete(this.timerId);
+ this.timerId = undefined;
}
cancel() {
if (this.isActive()) {
- window.clearTimeout(this.timer);
- if (this.timer) _testOnly_allTasks.delete(this.timer);
- this.timer = undefined;
+ this.cancelTimer();
+ this.resolvePromise?.(ResolvedDelayedTaskStatus.TASK_CANCELLED);
}
}
flush() {
if (this.isActive()) {
- this.cancel();
+ this.cancelTimer();
if (this.callback) this.callback();
+ this.resolvePromise?.(ResolvedDelayedTaskStatus.CALLBACK_EXECUTED);
}
}
isActive() {
- return this.timer !== undefined;
+ return this.timerId !== undefined;
}
}