Merge changes I890b6c61,I8d117211
* changes:
DeleteZombieCommentsRefs: allow the program to run in logging mode
DeleteZombieCommentsRefs: add a logging statement of the deleted drafts
diff --git a/java/com/google/gerrit/asciidoctor/DocIndexer.java b/java/com/google/gerrit/asciidoctor/DocIndexer.java
index 513bdd7..70857c0 100644
--- a/java/com/google/gerrit/asciidoctor/DocIndexer.java
+++ b/java/com/google/gerrit/asciidoctor/DocIndexer.java
@@ -42,8 +42,8 @@
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.IndexInput;
-import org.apache.lucene.store.RAMDirectory;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
@@ -92,9 +92,9 @@
}
}
- private RAMDirectory index()
+ private ByteBuffersDirectory index()
throws IOException, UnsupportedEncodingException, FileNotFoundException {
- RAMDirectory directory = new RAMDirectory();
+ ByteBuffersDirectory directory = new ByteBuffersDirectory();
IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer(CharArraySet.EMPTY_SET));
config.setOpenMode(OpenMode.CREATE);
config.setCommitOnClose(true);
@@ -132,7 +132,7 @@
return directory;
}
- private byte[] zip(RAMDirectory dir) throws IOException {
+ private byte[] zip(ByteBuffersDirectory dir) throws IOException {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
try (ZipOutputStream zip = new ZipOutputStream(buf)) {
for (String name : dir.listAll()) {
diff --git a/java/com/google/gerrit/lucene/LuceneAccountIndex.java b/java/com/google/gerrit/lucene/LuceneAccountIndex.java
index fe3fc15..7ed28f1 100644
--- a/java/com/google/gerrit/lucene/LuceneAccountIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneAccountIndex.java
@@ -50,9 +50,9 @@
import org.apache.lucene.search.SearcherFactory;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
+import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
-import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.BytesRef;
import org.eclipse.jgit.lib.Config;
@@ -84,7 +84,7 @@
private static Directory dir(Schema<AccountState> schema, Config cfg, SitePaths sitePaths)
throws IOException {
if (LuceneIndexModule.isInMemoryTest(cfg)) {
- return new RAMDirectory();
+ return new ByteBuffersDirectory();
}
Path indexDir = LuceneVersionManager.getDir(sitePaths, ACCOUNTS, schema);
return FSDirectory.open(indexDir);
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index cf176ee..ff2bd53 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -83,7 +83,7 @@
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldDocs;
-import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.util.BytesRef;
import org.eclipse.jgit.lib.Config;
@@ -170,7 +170,7 @@
new ChangeSubIndex(
schema,
sitePaths,
- new RAMDirectory(),
+ new ByteBuffersDirectory(),
"ramOpen",
skipFields,
openConfig,
@@ -180,7 +180,7 @@
new ChangeSubIndex(
schema,
sitePaths,
- new RAMDirectory(),
+ new ByteBuffersDirectory(),
"ramClosed",
skipFields,
closedConfig,
diff --git a/java/com/google/gerrit/lucene/LuceneGroupIndex.java b/java/com/google/gerrit/lucene/LuceneGroupIndex.java
index f7a2248..02d5a8b 100644
--- a/java/com/google/gerrit/lucene/LuceneGroupIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneGroupIndex.java
@@ -46,9 +46,9 @@
import org.apache.lucene.search.SearcherFactory;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
+import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
-import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.BytesRef;
import org.eclipse.jgit.lib.Config;
@@ -74,7 +74,7 @@
private static Directory dir(Schema<?> schema, Config cfg, SitePaths sitePaths)
throws IOException {
if (LuceneIndexModule.isInMemoryTest(cfg)) {
- return new RAMDirectory();
+ return new ByteBuffersDirectory();
}
Path indexDir = LuceneVersionManager.getDir(sitePaths, GROUPS, schema);
return FSDirectory.open(indexDir);
diff --git a/java/com/google/gerrit/lucene/LuceneProjectIndex.java b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
index 11707be..081e259 100644
--- a/java/com/google/gerrit/lucene/LuceneProjectIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
@@ -47,9 +47,9 @@
import org.apache.lucene.search.SearcherFactory;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
+import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
-import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.BytesRef;
import org.eclipse.jgit.lib.Config;
@@ -74,7 +74,7 @@
private static Directory dir(Schema<ProjectData> schema, Config cfg, SitePaths sitePaths)
throws IOException {
if (LuceneIndexModule.isInMemoryTest(cfg)) {
- return new RAMDirectory();
+ return new ByteBuffersDirectory();
}
Path indexDir = LuceneVersionManager.getDir(sitePaths, PROJECTS, schema);
return FSDirectory.open(indexDir);
diff --git a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
index 59ae6f8..7d3ddf1 100644
--- a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
+++ b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -33,9 +33,9 @@
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexOutput;
-import org.apache.lucene.store.RAMDirectory;
@Singleton
public class QueryDocumentationExecutor {
@@ -100,7 +100,7 @@
}
protected Directory readIndexDirectory() throws IOException {
- Directory dir = new RAMDirectory();
+ Directory dir = new ByteBuffersDirectory();
byte[] buffer = new byte[4096];
InputStream index = getClass().getResourceAsStream(Constants.INDEX_ZIP);
if (index == null) {
diff --git a/java/com/google/gerrit/server/restapi/project/UpdateSubmitRequirement.java b/java/com/google/gerrit/server/restapi/project/UpdateSubmitRequirement.java
index a176bc4..bbd617c 100644
--- a/java/com/google/gerrit/server/restapi/project/UpdateSubmitRequirement.java
+++ b/java/com/google/gerrit/server/restapi/project/UpdateSubmitRequirement.java
@@ -21,7 +21,6 @@
import com.google.gerrit.extensions.common.SubmitRequirementInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.CurrentUser;
@@ -107,14 +106,11 @@
return Response.created(SubmitRequirementJson.format(submitRequirement));
} catch (ConfigInvalidException e) {
throw new IOException("Failed to read project config", e);
- } catch (ResourceConflictException e) {
- throw new BadRequestException("Failed to create submit requirement", e);
}
}
public SubmitRequirement createSubmitRequirement(
- ProjectConfig config, String name, SubmitRequirementInput input)
- throws BadRequestException, ResourceConflictException {
+ ProjectConfig config, String name, SubmitRequirementInput input) throws BadRequestException {
validateSRName(name);
if (Strings.isNullOrEmpty(input.submittabilityExpression)) {
throw new BadRequestException("submittability_expression is required");
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow.ts
index 7bdb741..0bbddc4 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-bulk-vote-flow/gr-change-list-bulk-vote-flow.ts
@@ -38,6 +38,7 @@
import {allSettled} from '../../../utils/async-util';
import {pluralize} from '../../../utils/string-util';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
@customElement('gr-change-list-bulk-vote-flow')
export class GrChangeListBulkVoteFlow extends LitElement {
@@ -53,6 +54,8 @@
@query('#actionOverlay') actionOverlay!: GrOverlay;
+ @query('gr-dialog') dialog?: GrDialog;
+
@state() account?: AccountInfo;
static override get styles() {
@@ -153,10 +156,7 @@
permittedLabels
).filter(label => !triggerLabels.some(l => l.name === label.name));
return html`
- <gr-button
- id="voteFlowButton"
- flatten
- @click=${() => this.actionOverlay.open()}
+ <gr-button id="voteFlowButton" flatten @click=${this.openOverlay}
>Vote</gr-button
>
<gr-overlay id="actionOverlay" with-backdrop="">
@@ -219,6 +219,14 @@
}
}
+ private async openOverlay() {
+ await this.actionOverlay.open();
+ this.actionOverlay.setFocusStops({
+ start: queryAndAssert(this.dialog, 'header'),
+ end: queryAndAssert(this.dialog, 'footer'),
+ });
+ }
+
private renderErrors() {
if (getOverallStatus(this.progressByChange) !== ProgressStatus.FAILED) {
return nothing;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary.ts
index 6827870..1bfab48 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary.ts
@@ -105,7 +105,7 @@
return this.renderState(
iconForStatus(SubmitRequirementStatus.UNSATISFIED),
- this.renderSummary(numUnsatisfied, numRequirements)
+ this.renderSummary(numUnsatisfied)
);
}
@@ -118,11 +118,8 @@
>`;
}
- renderSummary(numUnsatisfied: number, numRequirements: number) {
- return html`<span
- ><span class="unsatisfied">${numUnsatisfied}</span
- ><span class="total">(of ${numRequirements})</span></span
- >`;
+ renderSummary(numUnsatisfied: number) {
+ return html`<span class="unsatisfied">${numUnsatisfied} missing</span>`;
}
renderCommentIcon() {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary_test.ts
index e5e27ce..bf09400 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary_test.ts
@@ -65,10 +65,7 @@
<gr-submit-requirement-dashboard-hovercard>
</gr-submit-requirement-dashboard-hovercard>
<iron-icon class="block" icon="gr-icons:block" role="img"></iron-icon>
- <span>
- <span class="unsatisfied">1</span>
- <span class="total">(of 1)</span>
- </span>
+ <span class="unsatisfied">1 missing</span>
</span>`);
});
@@ -89,10 +86,7 @@
<gr-submit-requirement-dashboard-hovercard>
</gr-submit-requirement-dashboard-hovercard>
<iron-icon class="block" icon="gr-icons:block" role="img"></iron-icon>
- <span>
- <span class="unsatisfied">1</span>
- <span class="total">(of 1)</span>
- </span>
+ <span class="unsatisfied">1 missing</span>
</span>
<iron-icon
class="commentIcon"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
index ea51514..230244e3 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
@@ -37,10 +37,11 @@
} from '../../shared/gr-account-list/gr-account-list';
import '@polymer/iron-icon/iron-icon';
import {getReplyByReason} from '../../../utils/attention-set-util';
-import {intersection} from '../../../utils/common-util';
+import {intersection, queryAndAssert} from '../../../utils/common-util';
import {accountOrGroupKey} from '../../../utils/account-util';
import {ValueChangedEvent} from '../../../types/events';
import {fireAlert, fireReload} from '../../../utils/event-util';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
@customElement('gr-change-list-reviewer-flow')
export class GrChangeListReviewerFlow extends LitElement {
@@ -88,6 +89,8 @@
@query('gr-overlay#confirm-cc') private ccConfirmOverlay?: GrOverlay;
+ @query('gr-dialog') dialog?: GrDialog;
+
private readonly reportingService = getAppContext().reportingService;
private getBulkActionsModel = resolve(this, bulkActionsModelToken);
@@ -360,10 +363,15 @@
.map(reviewer => getDisplayName(this.serverConfig, reviewer));
}
- private openOverlay() {
+ private async openOverlay() {
this.resetFlow();
this.isOverlayOpen = true;
- this.overlay?.open();
+ // Must await the overlay opening because the dialog is lazily rendered.
+ await this.overlay?.open();
+ this.overlay?.setFocusStops({
+ start: queryAndAssert(this.dialog, 'header'),
+ end: queryAndAssert(this.dialog, 'footer'),
+ });
}
private closeOverlay() {
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 c33a50e..35b1751 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
@@ -29,8 +29,7 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, html, css, nothing} 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 {Shortcut, ShortcutController} from '../../lit/shortcut-controller';
import {queryAll} from '../../../utils/common-util';
import {GrChangeListSection} from '../gr-change-list-section/gr-change-list-section';
import {Execution} from '../../../constants/reporting';
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index ac487c3..8149f5a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -34,10 +34,6 @@
import '../../checks/gr-checks-tab';
import {ChangeStarToggleStarDetail} from '../../shared/gr-change-star/gr-change-star';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {
- Shortcut,
- ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {GrEditConstants} from '../../edit/gr-edit-constants';
import {pluralize} from '../../../utils/string-util';
import {querySelectorAll, windowLocationReload} from '../../../utils/dom-util';
@@ -160,7 +156,11 @@
getRemovedByReason,
hasAttention,
} from '../../../utils/attention-set-util';
-import {shortcutsServiceToken} from '../../../services/shortcuts/shortcuts-service';
+import {
+ Shortcut,
+ ShortcutSection,
+ shortcutsServiceToken,
+} from '../../../services/shortcuts/shortcuts-service';
import {LoadingStatus} from '../../../models/change/change-model';
import {commentsModelToken} from '../../../models/comments/comments-model';
import {resolve} from '../../../models/dependency';
@@ -2087,10 +2087,6 @@
if (this.params.basePatchNum === undefined)
this.params.basePatchNum = PARENT;
- if (this.params.patchNum === undefined) {
- this.params.patchNum = computeLatestPatchNum(this.allPatchSets);
- }
-
const patchChanged = this.hasPatchRangeChanged(this.params);
let patchNumChanged = this.hasPatchNumChanged(this.params);
@@ -2121,7 +2117,7 @@
if (patchChanged) {
// We need to collapse all diffs when params change so that a non
// existing diff is not requested. See Issue 125270 for more details.
- this.fileList?.resetNumFilesShown();
+ this.fileList?.resetFileState();
this.fileList?.collapseAllDiffs();
this.reloadPatchNumDependentResources(patchNumChanged).then(() => {
this.sendShowChangeEvent();
@@ -2146,7 +2142,7 @@
this.updateComplete.then(() => {
assertIsDefined(this.fileList);
this.fileList?.collapseAllDiffs();
- this.fileList?.resetNumFilesShown();
+ this.fileList?.resetFileState();
});
// If the change was loaded before, then we are firing a 'reload' event
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index 32feb6a..712e0c8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -1354,6 +1354,7 @@
assertIsDefined(element.fileList);
assert.equal(element.fileList.numFilesShown, DEFAULT_NUM_FILES_SHOWN);
element.fileList.numFilesShown = 150;
+ element.fileList.selectedIndex = 15;
await element.updateComplete;
element.changeNum = 2 as NumericChangeId;
@@ -1363,6 +1364,7 @@
};
await element.updateComplete;
assert.equal(element.fileList.numFilesShown, DEFAULT_NUM_FILES_SHOWN);
+ assert.equal(element.fileList.selectedIndex, 0);
});
test('don’t reload entire page when patchRange changes', async () => {
@@ -1386,6 +1388,7 @@
assert.isTrue(reloadStub.calledOnce);
element.initialLoadComplete = true;
+ element.fileList.selectedIndex = 15;
element.change = {
...createChangeViewChange(),
revisions: {
@@ -1399,6 +1402,7 @@
element.params = {...value};
await element.updateComplete;
await flush();
+ assert.equal(element.fileList.selectedIndex, 0);
assert.isFalse(reloadStub.calledTwice);
assert.isTrue(reloadPatchDependentStub.calledOnce);
assert.isTrue(collapseStub.calledTwice);
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index 762a90a..209c3b7 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -28,15 +28,15 @@
import {GrDiffModeSelector} from '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
import {GrButton} from '../../shared/gr-button/gr-button';
import {fireEvent} from '../../../utils/event-util';
-import {
- Shortcut,
- ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {css, html, LitElement} from 'lit';
import {sharedStyles} from '../../../styles/shared-styles';
import {when} from 'lit/directives/when';
import {ifDefined} from 'lit/directives/if-defined';
-import {shortcutsServiceToken} from '../../../services/shortcuts/shortcuts-service';
+import {
+ Shortcut,
+ ShortcutSection,
+ shortcutsServiceToken,
+} from '../../../services/shortcuts/shortcuts-service';
import {resolve} from '../../../models/dependency';
import {getAppContext} from '../../../services/app-context';
import {subscribe} from '../../lit/subscription-controller';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 4179ad1..88255b7 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -188,8 +188,7 @@
@property({type: Object})
changeComments?: ChangeComments;
- @property({type: Number, attribute: 'selected-index'})
- selectedIndex = -1;
+ @state() selectedIndex = 0;
@property({type: Object})
change?: ParsedChangeInfo;
@@ -1535,8 +1534,10 @@
);
}
- resetNumFilesShown() {
+ resetFileState() {
this.numFilesShown = DEFAULT_NUM_FILES_SHOWN;
+ this.selectedIndex = 0;
+ this.fileCursor.setCursorAtIndex(this.selectedIndex, true);
}
openDiffPrefs() {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
index d96ad74..aab1afc 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
@@ -986,7 +986,6 @@
test('r key sets reviewed flag', async () => {
await element.updateComplete;
- element.handleCursorNext(new KeyboardEvent('keydown'));
pressKey(element, 'r');
await element.updateComplete;
@@ -998,7 +997,6 @@
element.reviewed = ['/COMMIT_MSG'];
await element.updateComplete;
- element.handleCursorNext(new KeyboardEvent('keydown'));
pressKey(element, 'r');
await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
index 14a87ec..32bd07a 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -8,10 +8,6 @@
import '../../shared/gr-icons/gr-icons';
import '../gr-message/gr-message';
import '../../../styles/gr-paper-styles';
-import {
- Shortcut,
- ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {parseDate} from '../../../utils/date-util';
import {MessageTag} from '../../../constants/constants';
import {getAppContext} from '../../../services/app-context';
@@ -43,7 +39,11 @@
import {paperStyles} from '../../../styles/gr-paper-styles';
import {when} from 'lit/directives/when';
import {ifDefined} from 'lit/directives/if-defined';
-import {shortcutsServiceToken} from '../../../services/shortcuts/shortcuts-service';
+import {
+ Shortcut,
+ ShortcutSection,
+ shortcutsServiceToken,
+} from '../../../services/shortcuts/shortcuts-service';
/**
* The content of the enum is also used in the UI for the button text.
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index f6ff2a8..8cd93c5 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -684,7 +684,7 @@
}
override disconnectedCallback() {
- this.storeTask?.cancel();
+ this.storeTask?.flush();
for (const cleanup of this.cleanups) cleanup();
this.cleanups = [];
super.disconnectedCallback();
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
index dbd428a..cc8d6c2 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
@@ -19,7 +19,7 @@
extractAssociatedLabels,
getApprovalInfo,
hasVotes,
- iconForStatus,
+ iconForRequirement,
} from '../../../utils/label-util';
import {ParsedChangeInfo} from '../../../types/types';
import {css, html, LitElement} from 'lit';
@@ -129,12 +129,9 @@
override render() {
if (!this.requirement) return;
- const icon = iconForStatus(this.requirement.status);
return html` <div id="container" role="tooltip" tabindex="-1">
<div class="section">
- <div class="sectionIcon">
- <iron-icon class=${icon} icon="gr-icons:${icon}"></iron-icon>
- </div>
+ <div class="sectionIcon">${this.renderStatus(this.requirement)}</div>
<div class="sectionContent">
<h3 class="name heading-3">
<span>${this.requirement.name}</span>
@@ -158,6 +155,16 @@
</div>`;
}
+ private renderStatus(requirement: SubmitRequirementResultInfo) {
+ const icon = iconForRequirement(requirement);
+ return html`<iron-icon
+ class=${icon}
+ icon="gr-icons:${icon}"
+ role="img"
+ aria-label=${requirement.status.toLowerCase()}
+ ></iron-icon>`;
+ }
+
private renderDescription() {
let description = this.requirement?.description;
if (this.requirement?.status === SubmitRequirementStatus.ERROR) {
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_test.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_test.ts
index 2b2d68d..7670ee80 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_test.ts
@@ -41,6 +41,8 @@
<div class="section">
<div class="sectionIcon">
<iron-icon
+ aria-label="satisfied"
+ role="img"
class="check-circle-filled"
icon="gr-icons:check-circle-filled"
>
@@ -88,6 +90,8 @@
<div class="section">
<div class="sectionIcon">
<iron-icon
+ aria-label="satisfied"
+ role="img"
class="check-circle-filled"
icon="gr-icons:check-circle-filled"
>
@@ -167,6 +171,8 @@
<div class="section">
<div class="sectionIcon">
<iron-icon
+ aria-label="satisfied"
+ role="img"
class="check-circle-filled"
icon="gr-icons:check-circle-filled"
>
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index af08d68..0941096 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -27,7 +27,7 @@
getTriggerVotes,
hasNeutralStatus,
hasVotes,
- iconForStatus,
+ iconForRequirement,
orderSubmitRequirements,
} from '../../../utils/label-util';
import {fontStyles} from '../../../styles/gr-font-styles';
@@ -191,7 +191,7 @@
index: number
) {
const row = html`
- <td>${this.renderStatus(requirement.status)}</td>
+ <td>${this.renderStatus(requirement)}</td>
<td class="name">
<gr-limited-text
class="name"
@@ -242,13 +242,13 @@
</gr-endpoint-decorator>`;
}
- renderStatus(status: SubmitRequirementStatus) {
- const icon = iconForStatus(status);
+ private renderStatus(requirement: SubmitRequirementResultInfo) {
+ const icon = iconForRequirement(requirement);
return html`<iron-icon
class=${icon}
icon="gr-icons:${icon}"
role="img"
- aria-label=${status.toLowerCase()}
+ aria-label=${requirement.status.toLowerCase()}
></iron-icon>`;
}
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
index 73e5da3..de1fd3e 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.ts
@@ -12,8 +12,6 @@
import {
ShortcutSection,
SectionView,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
-import {
shortcutsServiceToken,
ShortcutViewListener,
} from '../../../services/shortcuts/shortcuts-service';
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
index 4099979..e04c48c 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.ts
@@ -9,7 +9,7 @@
import {
SectionView,
ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+} from '../../../services/shortcuts/shortcuts-service';
const basicFixture = fixtureFromElement('gr-keyboard-shortcuts-dialog');
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 5de2190..e614ebe 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
@@ -10,9 +10,7 @@
AutocompleteSuggestion,
GrAutocomplete,
} from '../../shared/gr-autocomplete/gr-autocomplete';
-import {getDocsBaseUrl} from '../../../utils/url-util';
import {MergeabilityComputationBehavior} from '../../../constants/constants';
-import {getAppContext} from '../../../services/app-context';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, html, css} from 'lit';
import {
@@ -21,10 +19,12 @@
state,
query as queryDec,
} from 'lit/decorators';
-import {ShortcutController} from '../../lit/shortcut-controller';
+import {Shortcut, ShortcutController} from '../../lit/shortcut-controller';
import {query as queryUtil} from '../../../utils/common-util';
import {assertIsDefined} from '../../../utils/common-util';
-import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
+import {configModelToken} from '../../../models/config/config-model';
+import {resolve} from '../../../models/dependency';
+import {subscribe} from '../../lit/subscription-controller';
// Possible static search options for auto complete, without negations.
const SEARCH_OPERATORS: ReadonlyArray<string> = [
@@ -153,9 +153,12 @@
@property({type: Object})
accountSuggestions: SuggestionProvider = () => Promise.resolve([]);
- @property({type: Object})
+ @state()
serverConfig?: ServerInfo;
+ @state()
+ mergeabilityComputationBehavior?: MergeabilityComputationBehavior;
+
@property({type: String})
label = '';
@@ -163,22 +166,32 @@
@state() inputVal = '';
// private but used in test
- @state() docBaseUrl: string | null = null;
+ @state() docsBaseUrl: string | null = null;
@state() private query: AutocompleteQuery;
@state() private threshold = 1;
- private searchOperators = new Set(SEARCH_OPERATORS_WITH_NEGATIONS_SET);
-
- private readonly restApiService = getAppContext().restApiService;
-
private readonly shortcuts = new ShortcutController(this);
+ private readonly getConfigModel = resolve(this, configModelToken);
+
constructor() {
super();
this.query = (input: string) => this.getSearchSuggestions(input);
this.shortcuts.addAbstract(Shortcut.SEARCH, () => this.handleSearch());
+ subscribe(
+ this,
+ () => this.getConfigModel().mergeabilityComputationBehavior$,
+ mergeabilityComputationBehavior => {
+ this.mergeabilityComputationBehavior = mergeabilityComputationBehavior;
+ }
+ );
+ subscribe(
+ this,
+ () => this.getConfigModel().docsBaseUrl$,
+ docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
+ );
}
static override get styles() {
@@ -237,47 +250,34 @@
}
override willUpdate(changedProperties: PropertyValues) {
- if (changedProperties.has('serverConfig')) {
- this.serverConfigChanged();
- }
-
if (changedProperties.has('value')) {
this.valueChanged();
}
}
- private serverConfigChanged() {
- const mergeability =
- this.serverConfig?.change?.mergeability_computation_behavior;
- if (
- mergeability ===
- MergeabilityComputationBehavior.API_REF_UPDATED_AND_CHANGE_REINDEX ||
- mergeability ===
- MergeabilityComputationBehavior.REF_UPDATED_AND_CHANGE_REINDEX
- ) {
- // add 'is:mergeable' to searchOperators
- this.searchOperators.add('is:mergeable');
- this.searchOperators.add('-is:mergeable');
- } else {
- this.searchOperators.delete('is:mergeable');
- this.searchOperators.delete('-is:mergeable');
- }
- if (this.serverConfig) {
- getDocsBaseUrl(this.serverConfig, this.restApiService).then(baseUrl => {
- this.docBaseUrl = baseUrl;
- });
- }
- }
-
private valueChanged() {
this.inputVal = this.value;
}
+ private searchOperators() {
+ const set = new Set(SEARCH_OPERATORS_WITH_NEGATIONS_SET);
+ if (
+ this.mergeabilityComputationBehavior ===
+ MergeabilityComputationBehavior.API_REF_UPDATED_AND_CHANGE_REINDEX ||
+ this.mergeabilityComputationBehavior ===
+ MergeabilityComputationBehavior.REF_UPDATED_AND_CHANGE_REINDEX
+ ) {
+ set.add('is:mergeable');
+ set.add('-is:mergeable');
+ }
+ return set;
+ }
+
// private but used in test
computeHelpDocLink() {
// fallback to gerrit's official doc
let baseUrl =
- this.docBaseUrl ||
+ this.docsBaseUrl ||
'https://gerrit-review.googlesource.com/documentation/';
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
@@ -308,7 +308,7 @@
if (!this.inputVal) return;
const trimmedInput = this.inputVal.trim();
if (trimmedInput) {
- const predefinedOpOnlyQuery = [...this.searchOperators].some(
+ const predefinedOpOnlyQuery = [...this.searchOperators()].some(
op => op.endsWith(':') && op === trimmedInput
);
if (predefinedOpOnlyQuery) {
@@ -365,7 +365,7 @@
default:
return Promise.resolve(
- [...this.searchOperators]
+ [...this.searchOperators()]
.filter(operator => operator.includes(input))
.map(operator => {
return {text: operator};
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 90574a2..db46c22 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
@@ -8,11 +8,9 @@
import {GrSearchBar} from './gr-search-bar';
import '../../../scripts/util';
import {mockPromise, waitUntil} from '../../../test/test-utils';
-import {_testOnly_clearDocsBaseUrlCache} from '../../../utils/url-util';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import {
createChangeConfig,
- createGerritInfo,
createServerInfo,
} from '../../../test/test-data-generators';
import {MergeabilityComputationBehavior} from '../../../constants/constants';
@@ -176,14 +174,8 @@
suite('getSearchSuggestions', () => {
setup(async () => {
element = basicFixture.instantiate();
- element.serverConfig = {
- ...createServerInfo(),
- change: {
- ...createChangeConfig(),
- mergeability_computation_behavior:
- 'NEVER' as MergeabilityComputationBehavior,
- },
- };
+ element.mergeabilityComputationBehavior =
+ MergeabilityComputationBehavior.NEVER;
await element.updateComplete;
});
@@ -268,28 +260,21 @@
suite('doc url', () => {
setup(async () => {
- _testOnly_clearDocsBaseUrlCache();
element = basicFixture.instantiate();
- element.serverConfig = {
- ...createServerInfo(),
- gerrit: {
- ...createGerritInfo(),
- doc_url: 'https://doc.com/',
- },
- };
- await element.updateComplete;
});
- test('compute help doc url with correct path', () => {
- assert.equal(element.docBaseUrl, 'https://doc.com/');
+ test('compute help doc url with correct path', async () => {
+ element.docsBaseUrl = 'https://doc.com/';
+ await element.updateComplete;
assert.equal(
element.computeHelpDocLink(),
'https://doc.com/user-search.html'
);
});
- test('compute help doc url fallback to gerrit url', () => {
- element.docBaseUrl = null;
+ test('compute help doc url fallback to gerrit url', async () => {
+ element.docsBaseUrl = null;
+ await element.updateComplete;
assert.equal(
element.computeHelpDocLink(),
'https://gerrit-review.googlesource.com/documentation/' +
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 edee3cb..bd3bd3e 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
@@ -14,7 +14,10 @@
import {AutocompleteSuggestion} from '../../shared/gr-autocomplete/gr-autocomplete';
import {getAppContext} from '../../../services/app-context';
import {LitElement, html} from 'lit';
-import {customElement, property} from 'lit/decorators';
+import {customElement, property, state} from 'lit/decorators';
+import {subscribe} from '../../lit/subscription-controller';
+import {resolve} from '../../../models/dependency';
+import {configModelToken} from '../../../models/config/config-model';
const MAX_AUTOCOMPLETE_RESULTS = 10;
const SELF_EXPRESSION = 'self';
@@ -34,7 +37,7 @@
@property({type: String})
searchQuery = '';
- @property({type: Object})
+ @state()
serverConfig?: ServerInfo;
@property({type: String})
@@ -42,6 +45,19 @@
private readonly restApiService = getAppContext().restApiService;
+ private readonly getConfigModel = resolve(this, configModelToken);
+
+ constructor() {
+ super();
+ subscribe(
+ this,
+ () => this.getConfigModel().serverConfig$,
+ config => {
+ this.serverConfig = config;
+ }
+ );
+ }
+
override render() {
const accountSuggestions: SuggestionProvider = (predicate, expression) =>
this.fetchAccounts(predicate, expression);
@@ -57,7 +73,6 @@
.projectSuggestions=${projectSuggestions}
.groupSuggestions=${groupSuggestions}
.accountSuggestions=${accountSuggestions}
- .serverConfig=${this.serverConfig}
@handle-search=${(e: CustomEvent<SearchBarHandleSearchDetail>) => {
this.handleSearch(e);
}}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 6697113..be55097 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -22,14 +22,6 @@
import '../gr-patch-range-select/gr-patch-range-select';
import '../../change/gr-download-dialog/gr-download-dialog';
import '../../shared/gr-overlay/gr-overlay';
-import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {htmlTemplate} from './gr-diff-view_html';
-import {
- KeyboardShortcutMixin,
- Shortcut,
- ShortcutListener,
- ShortcutSection,
-} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {
GeneratedWebLink,
GerritNav,
@@ -50,7 +42,6 @@
specialFilePathCompare,
} from '../../../utils/path-list-util';
import {changeBaseURL, changeIsOpen} from '../../../utils/change-util';
-import {customElement, observe, property} from '@polymer/decorators';
import {GrDiffHost} from '../../diff/gr-diff-host/gr-diff-host';
import {
DropdownItem,
@@ -58,12 +49,10 @@
} from '../../shared/gr-dropdown-list/gr-dropdown-list';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
-import {GrDiffModeSelector} from '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
import {
BasePatchSetNum,
ChangeInfo,
CommitId,
- ConfigInfo,
EDIT,
FileInfo,
NumericChangeId,
@@ -85,7 +74,6 @@
ParsedChangeInfo,
} from '../../../types/types';
import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
-import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import {CommentSide, DiffViewMode, Side} from '../../../constants/constants';
import {GrApplyFixDialog} from '../gr-apply-fix-dialog/gr-apply-fix-dialog';
@@ -108,9 +96,10 @@
import {CursorMoveResult} from '../../../api/core';
import {isFalse, throttleWrap, until} from '../../../utils/async-util';
import {filter, take, switchMap} from 'rxjs/operators';
-import {combineLatest, Subscription} from 'rxjs';
+import {combineLatest} from 'rxjs';
import {
- listen,
+ Shortcut,
+ ShortcutSection,
shortcutsServiceToken,
} from '../../../services/shortcuts/shortcuts-service';
import {LoadingStatus} from '../../../models/change/change-model';
@@ -119,9 +108,17 @@
import {browserModelToken} from '../../../models/browser/browser-model';
import {commentsModelToken} from '../../../models/comments/comments-model';
import {changeModelToken} from '../../../models/change/change-model';
-import {resolve, DIPolymerElement} from '../../../models/dependency';
+import {resolve} from '../../../models/dependency';
import {BehaviorSubject} from 'rxjs';
-import {GrButton} from '../../shared/gr-button/gr-button';
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {ShortcutController} from '../../lit/shortcut-controller';
+import {subscribe} from '../../lit/subscription-controller';
+import {customElement, property, query, state} from 'lit/decorators';
+import {configModelToken} from '../../../models/config/config-model';
+import {a11yStyles} from '../../../styles/gr-a11y-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {ifDefined} from 'lit/directives/if-defined';
+import {when} from 'lit/directives/when';
const LOADING_BLAME = 'Loading blame...';
const LOADED_BLAME = 'Blame loaded';
@@ -139,32 +136,8 @@
previous: string | null;
next: string | null;
}
-
-export interface GrDiffView {
- $: {
- diffHost: GrDiffHost;
- reviewed: HTMLInputElement;
- dropdown: GrDropdownList;
- diffPreferencesDialog: GrOverlay;
- applyFixDialog: GrApplyFixDialog;
- modeSelect: GrDiffModeSelector;
- downloadOverlay: GrOverlay;
- downloadDialog: GrDownloadDialog;
- toggleBlame: GrButton;
- diffPrefsContainer: HTMLElement;
- rangeSelect: HTMLElement;
- };
-}
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = KeyboardShortcutMixin(DIPolymerElement);
-
@customElement('gr-diff-view')
-export class GrDiffView extends base {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrDiffView extends LitElement {
/**
* Fired when the title of the page should change.
*
@@ -176,105 +149,138 @@
*
* @event show-alert
*/
+ @query('#diffHost')
+ diffHost?: GrDiffHost;
- @property({type: Object, observer: '_paramsChanged'})
- params?: AppElementParams;
+ @query('#reviewed')
+ reviewed?: HTMLInputElement;
+
+ @query('#downloadOverlay')
+ downloadOverlay?: GrOverlay;
+
+ @query('#downloadDialog')
+ downloadDialog?: GrDownloadDialog;
+
+ @query('#dropdown')
+ dropdown?: GrDropdownList;
+
+ @query('#applyFixDialog')
+ applyFixDialog?: GrApplyFixDialog;
+
+ @query('#diffPreferencesDialog')
+ diffPreferencesDialog?: GrOverlay;
+
+ /**
+ * URL params passed from the router.
+ * Use params getter/setter.
+ */
+ private _params?: AppElementParams;
@property({type: Object})
- _patchRange?: PatchRange;
+ get params() {
+ return this._params;
+ }
- @property({type: Object})
- _commitRange?: CommitRange;
+ set params(params: AppElementParams | undefined) {
+ if (this._params === params) return;
+ const oldParams = this._params;
+ this._params = params;
+ this.paramsChanged();
+ this.requestUpdate('params', oldParams);
+ }
- @property({type: Object})
- _change?: ParsedChangeInfo;
+ // Private but used in tests.
+ @state()
+ patchRange?: PatchRange;
- @property({type: Object})
- _changeComments?: ChangeComments;
+ // Private but used in tests.
+ @state()
+ commitRange?: CommitRange;
- @property({type: String})
- _changeNum?: NumericChangeId;
+ // Private but used in tests.
+ @state()
+ change?: ParsedChangeInfo;
- @property({type: Object})
- _diff?: DiffInfo;
+ // Private but used in tests.
+ @state()
+ changeComments?: ChangeComments;
- @property({
- type: Array,
- computed: '_formatFilesForDropdown(_files, _patchRange, _changeComments)',
- })
- _formattedFiles?: DropdownItem[];
+ // Private but used in tests.
+ @state()
+ changeNum?: NumericChangeId;
- @property({type: Array, computed: '_getSortedFileList(_files)'})
- _fileList?: string[];
+ // Private but used in tests.
+ @state()
+ diff?: DiffInfo;
- @property({type: Object})
- _files: Files = {sortedFileList: [], changeFilesByPath: {}};
+ // TODO: Move to using files-model.
+ // Private but used in tests.
+ @state()
+ files: Files = {sortedFileList: [], changeFilesByPath: {}};
- @property({type: Object, computed: '_getCurrentFile(_files, _path)'})
- _file?: FileInfo;
-
- @property({type: String, observer: '_pathChanged'})
+ // Private but used in tests
+ // Use path getter/setter.
_path?: string;
- @property({type: Number, computed: '_computeFileNum(_path, _formattedFiles)'})
- _fileNum?: number;
+ get path() {
+ return this._path;
+ }
- @property({type: Boolean})
- _loggedIn = false;
+ set path(path: string | undefined) {
+ if (this._path === path) return;
+ const oldPath = this._path;
+ this._path = path;
+ this.pathChanged();
+ this.requestUpdate('path', oldPath);
+ }
- @property({type: Boolean})
- _loading = true;
+ // Private but used in tests.
+ @state()
+ loggedIn = false;
+
+ // Private but used in tests.
+ @state()
+ loading = true;
@property({type: Object})
- _prefs?: DiffPreferencesInfo;
+ prefs?: DiffPreferencesInfo;
- @property({type: Object})
- _projectConfig?: ConfigInfo;
+ @state()
+ private serverConfig?: ServerInfo;
- @property({type: Object})
- _serverConfig?: ServerInfo;
+ // Private but used in tests.
+ @state()
+ userPrefs?: PreferencesInfo;
- @property({type: Object})
- _userPrefs?: PreferencesInfo;
+ @state()
+ private isImageDiff?: boolean;
- @property({type: Boolean})
- _isImageDiff?: boolean;
+ @state()
+ private editWeblinks?: GeneratedWebLink[];
- @property({type: Object})
- _editWeblinks?: GeneratedWebLink[];
+ @state()
+ private filesWeblinks?: FilesWebLinks;
- @property({type: Object})
- _filesWeblinks?: FilesWebLinks;
+ // Private but used in tests.
+ @state()
+ commentMap?: CommentMap;
- @property({type: Object})
- _commentMap?: CommentMap;
+ @state()
+ private commentSkips?: CommentSkips;
- @property({
- type: Object,
- computed: '_computeCommentSkips(_commentMap, _fileList, _path)',
- })
- _commentSkips?: CommentSkips;
+ // Private but used in tests.
+ @state()
+ isBlameLoaded?: boolean;
- @property({type: Boolean, computed: '_computeEditMode(_patchRange.*)'})
- _editMode?: boolean;
+ @state()
+ private isBlameLoading = false;
- @property({type: Boolean})
- _isBlameLoaded?: boolean;
+ @state()
+ private allPatchSets?: PatchSet[] = [];
- @property({type: Boolean})
- _isBlameLoading = false;
-
- @property({
- type: Array,
- computed: '_computeAllPatchSets(_change, _change.revisions.*)',
- })
- _allPatchSets?: PatchSet[] = [];
-
- @property({type: Object, computed: '_getRevisionInfo(_change)'})
- _revisionInfo?: RevisionInfoObj;
-
- @property({type: Number})
- _focusLineNum?: number;
+ // Private but used in tests.
+ @state()
+ focusLineNum?: number;
/** Called in disconnectedCallback. */
private cleanups: (() => void)[] = [];
@@ -282,67 +288,6 @@
// visible for testing
reviewedFiles = new Set<string>();
- override keyboardShortcuts(): ShortcutListener[] {
- return [
- listen(Shortcut.LEFT_PANE, _ => this.cursor?.moveLeft()),
- listen(Shortcut.RIGHT_PANE, _ => this.cursor?.moveRight()),
- listen(Shortcut.NEXT_LINE, _ => this.handleNextLine()),
- listen(Shortcut.PREV_LINE, _ => this.handlePrevLine()),
- listen(Shortcut.VISIBLE_LINE, _ => this.cursor?.moveToVisibleArea()),
- listen(Shortcut.NEXT_FILE_WITH_COMMENTS, _ =>
- this.moveToNextFileWithComment()
- ),
- listen(Shortcut.PREV_FILE_WITH_COMMENTS, _ =>
- this.moveToPreviousFileWithComment()
- ),
- listen(Shortcut.NEW_COMMENT, _ => this.handleNewComment()),
- listen(Shortcut.SAVE_COMMENT, _ => {}),
- listen(Shortcut.NEXT_FILE, _ => this.handleNextFile()),
- listen(Shortcut.PREV_FILE, _ => this.handlePrevFile()),
- listen(Shortcut.NEXT_CHUNK, _ => this.handleNextChunk()),
- listen(Shortcut.PREV_CHUNK, _ => this.handlePrevChunk()),
- listen(Shortcut.NEXT_COMMENT_THREAD, _ => this.handleNextCommentThread()),
- listen(Shortcut.PREV_COMMENT_THREAD, _ => this.handlePrevCommentThread()),
- listen(Shortcut.OPEN_REPLY_DIALOG, _ => this.handleOpenReplyDialog()),
- listen(Shortcut.TOGGLE_LEFT_PANE, _ => this.handleToggleLeftPane()),
- listen(Shortcut.OPEN_DOWNLOAD_DIALOG, _ =>
- this.handleOpenDownloadDialog()
- ),
- listen(Shortcut.UP_TO_CHANGE, _ => this.handleUpToChange()),
- listen(Shortcut.OPEN_DIFF_PREFS, _ => this.handleCommaKey()),
- listen(Shortcut.TOGGLE_DIFF_MODE, _ => this.handleToggleDiffMode()),
- listen(Shortcut.TOGGLE_FILE_REVIEWED, e => {
- if (this._throttledToggleFileReviewed) {
- this._throttledToggleFileReviewed(e);
- }
- }),
- listen(Shortcut.TOGGLE_ALL_DIFF_CONTEXT, _ =>
- this.handleToggleAllDiffContext()
- ),
- listen(Shortcut.NEXT_UNREVIEWED_FILE, _ =>
- this.handleNextUnreviewedFile()
- ),
- listen(Shortcut.TOGGLE_BLAME, _ => this.handleToggleBlame()),
- listen(Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS, _ =>
- this._handleToggleHideAllCommentThreads()
- ),
- listen(Shortcut.OPEN_FILE_LIST, _ => this.handleOpenFileList()),
- listen(Shortcut.DIFF_AGAINST_BASE, _ => this.handleDiffAgainstBase()),
- listen(Shortcut.DIFF_AGAINST_LATEST, _ => this.handleDiffAgainstLatest()),
- listen(Shortcut.DIFF_BASE_AGAINST_LEFT, _ =>
- this.handleDiffBaseAgainstLeft()
- ),
- listen(Shortcut.DIFF_RIGHT_AGAINST_LATEST, _ =>
- this.handleDiffRightAgainstLatest()
- ),
- listen(Shortcut.DIFF_BASE_AGAINST_LATEST, _ =>
- this.handleDiffBaseAgainstLatest()
- ),
- listen(Shortcut.EXPAND_ALL_COMMENT_THREADS, _ => {}), // docOnly
- listen(Shortcut.COLLAPSE_ALL_COMMENT_THREADS, _ => {}), // docOnly
- ];
- }
-
private readonly reporting = getAppContext().reportingService;
private readonly restApiService = getAppContext().restApiService;
@@ -364,88 +309,170 @@
private readonly getShortcutsService = resolve(this, shortcutsServiceToken);
- _throttledToggleFileReviewed?: (e: KeyboardEvent) => void;
+ private readonly getConfigModel = resolve(this, configModelToken);
- _onRenderHandler?: EventListener;
+ private throttledToggleFileReviewed?: (e: KeyboardEvent) => void;
- // visible for testing
+ @state()
cursor?: GrDiffCursor;
- private subscriptions: Subscription[] = [];
-
private connected$ = new BehaviorSubject(false);
- override connectedCallback() {
- super.connectedCallback();
- this.connected$.next(true);
- this._throttledToggleFileReviewed = throttleWrap(_ =>
- this.handleToggleFileReviewed()
+ private readonly shortcutsController = new ShortcutController(this);
+
+ constructor() {
+ super();
+ this.setupKeyboardShortcuts();
+ this.setupSubscriptions();
+ }
+
+ private setupKeyboardShortcuts() {
+ const listen = (shortcut: Shortcut, fn: (e: KeyboardEvent) => void) => {
+ this.shortcutsController.addAbstract(shortcut, fn);
+ };
+ listen(Shortcut.LEFT_PANE, _ => this.cursor?.moveLeft());
+ listen(Shortcut.RIGHT_PANE, _ => this.cursor?.moveRight());
+ listen(Shortcut.NEXT_LINE, _ => this.handleNextLine());
+ listen(Shortcut.PREV_LINE, _ => this.handlePrevLine());
+ listen(Shortcut.VISIBLE_LINE, _ => this.cursor?.moveToVisibleArea());
+ listen(Shortcut.NEXT_FILE_WITH_COMMENTS, _ =>
+ this.moveToNextFileWithComment()
);
- this._getLoggedIn().then(loggedIn => {
- this._loggedIn = loggedIn;
+ listen(Shortcut.PREV_FILE_WITH_COMMENTS, _ =>
+ this.moveToPreviousFileWithComment()
+ );
+ listen(Shortcut.NEW_COMMENT, _ => this.handleNewComment());
+ listen(Shortcut.SAVE_COMMENT, _ => {});
+ listen(Shortcut.NEXT_FILE, _ => this.handleNextFile());
+ listen(Shortcut.PREV_FILE, _ => this.handlePrevFile());
+ listen(Shortcut.NEXT_CHUNK, _ => this.handleNextChunk());
+ listen(Shortcut.PREV_CHUNK, _ => this.handlePrevChunk());
+ listen(Shortcut.NEXT_COMMENT_THREAD, _ => this.handleNextCommentThread());
+ listen(Shortcut.PREV_COMMENT_THREAD, _ => this.handlePrevCommentThread());
+ listen(Shortcut.OPEN_REPLY_DIALOG, _ => this.handleOpenReplyDialog());
+ listen(Shortcut.TOGGLE_LEFT_PANE, _ => this.handleToggleLeftPane());
+ listen(Shortcut.OPEN_DOWNLOAD_DIALOG, _ => this.handleOpenDownloadDialog());
+ listen(Shortcut.UP_TO_CHANGE, _ => this.handleUpToChange());
+ listen(Shortcut.OPEN_DIFF_PREFS, _ => this.handleCommaKey());
+ listen(Shortcut.TOGGLE_DIFF_MODE, _ => this.handleToggleDiffMode());
+ listen(Shortcut.TOGGLE_FILE_REVIEWED, e => {
+ if (this.throttledToggleFileReviewed) {
+ this.throttledToggleFileReviewed(e);
+ }
});
- this.restApiService.getConfig().then(config => {
- this._serverConfig = config;
- });
+ listen(Shortcut.TOGGLE_ALL_DIFF_CONTEXT, _ =>
+ this.handleToggleAllDiffContext()
+ );
+ listen(Shortcut.NEXT_UNREVIEWED_FILE, _ => this.handleNextUnreviewedFile());
+ listen(Shortcut.TOGGLE_BLAME, _ => this.toggleBlame());
+ listen(Shortcut.TOGGLE_HIDE_ALL_COMMENT_THREADS, _ =>
+ this.handleToggleHideAllCommentThreads()
+ );
+ listen(Shortcut.OPEN_FILE_LIST, _ => this.handleOpenFileList());
+ listen(Shortcut.DIFF_AGAINST_BASE, _ => this.handleDiffAgainstBase());
+ listen(Shortcut.DIFF_AGAINST_LATEST, _ => this.handleDiffAgainstLatest());
+ listen(Shortcut.DIFF_BASE_AGAINST_LEFT, _ =>
+ this.handleDiffBaseAgainstLeft()
+ );
+ listen(Shortcut.DIFF_RIGHT_AGAINST_LATEST, _ =>
+ this.handleDiffRightAgainstLatest()
+ );
+ listen(Shortcut.DIFF_BASE_AGAINST_LATEST, _ =>
+ this.handleDiffBaseAgainstLatest()
+ );
+ listen(Shortcut.EXPAND_ALL_COMMENT_THREADS, _ => {}); // docOnly
+ listen(Shortcut.COLLAPSE_ALL_COMMENT_THREADS, _ => {}); // docOnly
+ }
- this.subscriptions.push(
- this.getCommentsModel().changeComments$.subscribe(changeComments => {
- this._changeComments = changeComments;
- })
+ private setupSubscriptions() {
+ subscribe(
+ this,
+ () => this.userModel.loggedIn$,
+ loggedIn => {
+ this.loggedIn = loggedIn;
+ }
);
-
- this.subscriptions.push(
- this.userModel.preferences$.subscribe(preferences => {
- this._userPrefs = preferences;
- })
+ subscribe(
+ this,
+ () => this.getConfigModel().serverConfig$,
+ config => {
+ this.serverConfig = config;
+ }
);
- this.subscriptions.push(
- this.userModel.diffPreferences$.subscribe(diffPreferences => {
- this._prefs = diffPreferences;
- })
+ subscribe(
+ this,
+ () => this.getCommentsModel().changeComments$,
+ changeComments => {
+ this.changeComments = changeComments;
+ }
);
- this.subscriptions.push(
- this.getChangeModel().change$.subscribe(change => {
- // The diff view is tied to a specfic change number, so don't update
- // _change to undefined.
- if (change) this._change = change;
- })
+ subscribe(
+ this,
+ () => this.userModel.preferences$,
+ preferences => {
+ this.userPrefs = preferences;
+ }
);
-
- this.subscriptions.push(
- this.getChangeModel().reviewedFiles$.subscribe(reviewedFiles => {
+ subscribe(
+ this,
+ () => this.userModel.diffPreferences$,
+ diffPreferences => {
+ this.prefs = diffPreferences;
+ }
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().change$,
+ change => {
+ // The diff view is tied to a specific change number, so don't update
+ // change to undefined.
+ if (change) this.change = change;
+ }
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().reviewedFiles$,
+ reviewedFiles => {
this.reviewedFiles = new Set(reviewedFiles) ?? new Set();
- })
+ }
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().diffPath$,
+ path => (this.path = path)
);
- this.subscriptions.push(
- this.getChangeModel().diffPath$.subscribe(path => (this._path = path))
- );
-
- this.subscriptions.push(
- combineLatest(
- this.getChangeModel().diffPath$,
- this.getChangeModel().reviewedFiles$
- ).subscribe(([path, files]) => {
- this.$.reviewed.checked = !!path && !!files && files.includes(path);
- })
+ subscribe(
+ this,
+ () =>
+ combineLatest([
+ this.getChangeModel().diffPath$,
+ this.getChangeModel().reviewedFiles$,
+ ]),
+ ([path, files]) => {
+ this.updateComplete.then(() => {
+ assertIsDefined(this.reviewed, 'reviewed');
+ this.reviewed.checked = !!path && !!files && files.includes(path);
+ });
+ }
);
// When user initially loads the diff view, we want to autmatically mark
// the file as reviewed if they have it enabled. We can't observe these
// properties since the method will be called anytime a property updates
// but we only want to call this on the initial load.
- this.subscriptions.push(
- this.getChangeModel()
- .diffPath$.pipe(
+ subscribe(
+ this,
+ () =>
+ this.getChangeModel().diffPath$.pipe(
filter(diffPath => !!diffPath),
switchMap(() =>
- combineLatest(
+ combineLatest([
this.getChangeModel().patchNum$,
this.routerModel.routerView$,
this.userModel.diffPreferences$,
- this.getChangeModel().reviewedFiles$
- ).pipe(
+ this.getChangeModel().reviewedFiles$,
+ ]).pipe(
filter(
([patchNum, routerView, diffPrefs, reviewedFiles]) =>
!!patchNum &&
@@ -456,245 +483,720 @@
take(1)
)
)
- )
- .subscribe(([patchNum, _routerView, diffPrefs]) => {
- this.setReviewedStatus(patchNum!, diffPrefs);
- })
+ ),
+ ([patchNum, _routerView, diffPrefs]) => {
+ this.setReviewedStatus(patchNum!, diffPrefs);
+ }
);
- this.subscriptions.push(
- this.getChangeModel().diffPath$.subscribe(path => (this._path = path))
+ subscribe(
+ this,
+ () => this.getChangeModel().diffPath$,
+ path => (this.path = path)
+ );
+ }
+
+ static override get styles() {
+ return [
+ a11yStyles,
+ sharedStyles,
+ css`
+ :host {
+ display: block;
+ background-color: var(--view-background-color);
+ }
+ .hidden {
+ display: none;
+ }
+ gr-patch-range-select {
+ display: block;
+ }
+ gr-diff {
+ border: none;
+ }
+ .stickyHeader {
+ background-color: var(--view-background-color);
+ position: sticky;
+ top: 0;
+ /* TODO(dhruvsri): This is required only because of 'position:relative' in
+ <gr-diff-highlight> (which could maybe be removed??). */
+ z-index: 1;
+ box-shadow: var(--elevation-level-1);
+ /* This is just for giving the box-shadow some space. */
+ margin-bottom: 2px;
+ }
+ header,
+ .subHeader {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ }
+ header {
+ padding: var(--spacing-s) var(--spacing-xl);
+ border-bottom: 1px solid var(--border-color);
+ }
+ .changeNumberColon {
+ color: transparent;
+ }
+ .headerSubject {
+ margin-right: var(--spacing-m);
+ font-weight: var(--font-weight-bold);
+ }
+ .patchRangeLeft {
+ align-items: center;
+ display: flex;
+ }
+ .navLink:not([href]) {
+ color: var(--deemphasized-text-color);
+ }
+ .navLinks {
+ align-items: center;
+ display: flex;
+ white-space: nowrap;
+ }
+ .navLink {
+ padding: 0 var(--spacing-xs);
+ }
+ .reviewed {
+ display: inline-block;
+ margin: 0 var(--spacing-xs);
+ vertical-align: top;
+ position: relative;
+ top: 8px;
+ }
+ .jumpToFileContainer {
+ display: inline-block;
+ word-break: break-all;
+ }
+ .mobile {
+ display: none;
+ }
+ gr-button {
+ padding: var(--spacing-s) 0;
+ text-decoration: none;
+ }
+ .loading {
+ color: var(--deemphasized-text-color);
+ font-family: var(--header-font-family);
+ font-size: var(--font-size-h1);
+ font-weight: var(--font-weight-h1);
+ line-height: var(--line-height-h1);
+ height: 100%;
+ padding: var(--spacing-l);
+ text-align: center;
+ }
+ .subHeader {
+ background-color: var(--background-color-secondary);
+ flex-wrap: wrap;
+ padding: 0 var(--spacing-l);
+ }
+ .prefsButton {
+ text-align: right;
+ }
+ .editMode .hideOnEdit {
+ display: none;
+ }
+ .blameLoader,
+ .fileNum {
+ display: none;
+ }
+ .blameLoader.show,
+ .fileNum.show,
+ .download,
+ .preferences,
+ .rightControls {
+ align-items: center;
+ display: flex;
+ }
+ .diffModeSelector,
+ .editButton {
+ align-items: center;
+ display: flex;
+ }
+ .diffModeSelector span,
+ .editButton span {
+ margin-right: var(--spacing-xs);
+ }
+ .diffModeSelector.hide,
+ .separator.hide {
+ display: none;
+ }
+ .editButtona a {
+ text-decoration: none;
+ }
+ @media screen and (max-width: 50em) {
+ header {
+ padding: var(--spacing-s) var(--spacing-l);
+ }
+ .dash {
+ display: none;
+ }
+ .desktop {
+ display: none;
+ }
+ .fileNav {
+ align-items: flex-start;
+ display: flex;
+ margin: 0 var(--spacing-xs);
+ }
+ .fullFileName {
+ display: block;
+ font-style: italic;
+ min-width: 50%;
+ padding: 0 var(--spacing-xxs);
+ text-align: center;
+ width: 100%;
+ word-wrap: break-word;
+ }
+ .reviewed {
+ vertical-align: -1px;
+ }
+ .mobileNavLink {
+ color: var(--primary-text-color);
+ font-family: var(--header-font-family);
+ font-size: var(--font-size-h2);
+ font-weight: var(--font-weight-h2);
+ line-height: var(--line-height-h2);
+ text-decoration: none;
+ }
+ .mobileNavLink:not([href]) {
+ color: var(--deemphasized-text-color);
+ }
+ .jumpToFileContainer {
+ display: block;
+ width: 100%;
+ word-break: break-all;
+ }
+ /* prettier formatter removes semi-colons after css mixins. */
+ /* prettier-ignore */
+ gr-dropdown-list {
+ width: 100%;
+ --gr-select-style-width: 100%;
+ --gr-select-style-display: block;
+ --native-select-style-width: 100%;
+ }
+ }
+ :host(.hideComments) {
+ --gr-comment-thread-display: none;
+ }
+ `,
+ ];
+ }
+
+ override connectedCallback() {
+ super.connectedCallback();
+ this.connected$.next(true);
+ this.throttledToggleFileReviewed = throttleWrap(_ =>
+ this.handleToggleFileReviewed()
);
this.addEventListener('open-fix-preview', e => this.onOpenFixPreview(e));
this.cursor = new GrDiffCursor();
- this._onRenderHandler = (_: Event) => {
- // We have to wait until render because at the time of connectedCallback,
- // gr-diff-host has not been rendered yet.
- this.cursor?.replaceDiffs([this.$.diffHost]);
- this.cursor?.reInitCursor();
- };
- this.$.diffHost.addEventListener('render', this._onRenderHandler);
this.cleanups.push(
- addGlobalShortcut(
- {key: Key.ESC},
- _ => (this.$.diffHost.displayLine = false)
- )
+ addGlobalShortcut({key: Key.ESC}, _ => {
+ assertIsDefined(this.diffHost, 'diffHost');
+ this.diffHost.displayLine = false;
+ })
);
}
override disconnectedCallback() {
this.cursor?.dispose();
- if (this._onRenderHandler) {
- this.$.diffHost.removeEventListener('render', this._onRenderHandler);
- this._onRenderHandler = undefined;
- }
for (const cleanup of this.cleanups) cleanup();
this.cleanups = [];
- for (const s of this.subscriptions) {
- s.unsubscribe();
- }
- this.subscriptions = [];
this.connected$.next(false);
super.disconnectedCallback();
}
+ protected override willUpdate(changedProperties: PropertyValues) {
+ super.willUpdate(changedProperties);
+ if (changedProperties.has('change')) {
+ this.allPatchSets = computeAllPatchSets(this.change);
+ }
+ if (
+ changedProperties.has('commentMap') ||
+ changedProperties.has('files') ||
+ changedProperties.has('path')
+ ) {
+ this.commentSkips = this.computeCommentSkips(
+ this.commentMap,
+ this.files?.sortedFileList,
+ this.path
+ );
+ }
+
+ if (
+ changedProperties.has('changeNum') ||
+ changedProperties.has('changeComments') ||
+ changedProperties.has('patchRange')
+ ) {
+ this.fetchFiles();
+ }
+ }
+
+ protected override updated(changedProperties: PropertyValues): void {
+ super.updated(changedProperties);
+ assertIsDefined(this.diffHost, 'diffHost');
+ if (
+ changedProperties.has('change') ||
+ changedProperties.has('changeNum') ||
+ changedProperties.has('cursor') ||
+ changedProperties.has('commitRange') ||
+ changedProperties.has('path') ||
+ changedProperties.has('patchRange') ||
+ changedProperties.has('files')
+ ) {
+ // We have to wait until render because at the time of updated,
+ // gr-diff-host has not been rendered yet.
+ this.diffHost.updateComplete.then(() => {
+ assertIsDefined(this.diffHost, 'diffHost');
+ this.cursor?.replaceDiffs([this.diffHost]);
+ this.cursor?.reInitCursor();
+ });
+ }
+ if (
+ changedProperties.has('changeComments') ||
+ changedProperties.has('path') ||
+ changedProperties.has('patchRange') ||
+ changedProperties.has('files')
+ ) {
+ if (this.changeComments && this.path && this.patchRange) {
+ assertIsDefined(this.diffHost);
+ const file = this.files?.changeFilesByPath
+ ? this.files.changeFilesByPath[this.path]
+ : undefined;
+ this.diffHost.updateComplete.then(() => {
+ assertIsDefined(this.path);
+ assertIsDefined(this.patchRange);
+ assertIsDefined(this.diffHost);
+ assertIsDefined(this.changeComments);
+ this.diffHost.threads = this.changeComments.getThreadsBySideForFile(
+ {path: this.path, basePath: file?.old_path},
+ this.patchRange
+ );
+ });
+ }
+ }
+ }
+
+ override render() {
+ const file = this.getFileRange();
+ return html`
+ ${this.renderStickyHeader()}
+ <div class="loading" ?hidden=${!this.loading}>Loading...</div>
+ <h2 class="assistive-tech-only">Diff view</h2>
+ <gr-diff-host
+ id="diffHost"
+ ?hidden=${this.loading}
+ .changeNum=${this.changeNum}
+ .change=${this.change}
+ .commitRange=${this.commitRange}
+ .patchRange=${this.patchRange}
+ .file=${file}
+ .path=${this.path}
+ .projectName=${this.change?.project}
+ @is-blame-loaded-changed=${this.onIsBlameLoadedChanged}
+ @comment-anchor-tap=${this.onLineSelected}
+ @line-selected=${this.onLineSelected}
+ @diff-changed=${this.onDiffChanged}
+ @edit-weblinks-changed=${this.onEditWeblinksChanged}
+ @files-weblinks-changed=${this.onFilesWeblinksChanged}
+ @is-image-diff-changed=${this.onIsImageDiffChanged}
+ >
+ </gr-diff-host>
+ ${this.renderDialogs()}
+ `;
+ }
+
+ private renderStickyHeader() {
+ return html` <div
+ class="stickyHeader ${this.computeEditMode() ? 'editMode' : ''}"
+ >
+ <h1 class="assistive-tech-only">
+ Diff of ${this.path ? computeTruncatedPath(this.path) : ''}
+ </h1>
+ <header>${this.renderHeader()}</header>
+ <div class="subHeader">
+ ${this.renderPatchRangeLeft()} ${this.renderRightControls()}
+ </div>
+ <div class="fileNav mobile">
+ <a class="mobileNavLink" href=${ifDefined(this.computeNavLinkURL(-1))}
+ ><</a
+ >
+ <div class="fullFileName mobile">${computeDisplayPath(this.path)}</div>
+ <a class="mobileNavLink" href=${ifDefined(this.computeNavLinkURL(1))}
+ >></a
+ >
+ </div>
+ </div>`;
+ }
+
+ private renderHeader() {
+ const formattedFiles = this.formatFilesForDropdown();
+ const fileNum = this.computeFileNum(formattedFiles);
+ const fileNumClass = this.computeFileNumClass(fileNum, formattedFiles);
+ return html` <div>
+ <a href=${this.getChangePath()}>${this.changeNum}</a
+ ><span class="changeNumberColon">:</span>
+ <span class="headerSubject">${this.change?.subject}</span>
+ <input
+ id="reviewed"
+ class="reviewed hideOnEdit"
+ type="checkbox"
+ ?hidden=${!this.loggedIn}
+ title="Toggle reviewed status of file"
+ aria-label="file reviewed"
+ @change=${this.handleReviewedChange}
+ />
+ <div class="jumpToFileContainer">
+ <gr-dropdown-list
+ id="dropdown"
+ .value=${this.path}
+ .items=${formattedFiles}
+ initial-count="75"
+ show-copy-for-trigger-text
+ @value-change=${this.handleFileChange}
+ ></gr-dropdown-list>
+ </div>
+ </div>
+ <div class="navLinks desktop">
+ <span class="fileNum ${ifDefined(fileNumClass)}">
+ File ${fileNum} of ${formattedFiles.length}
+ <span class="separator"></span>
+ </span>
+ <a
+ class="navLink"
+ title=${this.createTitle(
+ Shortcut.PREV_FILE,
+ ShortcutSection.NAVIGATION
+ )}
+ href=${ifDefined(this.computeNavLinkURL(-1))}
+ >Prev</a
+ >
+ <span class="separator"></span>
+ <a
+ class="navLink"
+ title=${this.createTitle(
+ Shortcut.UP_TO_CHANGE,
+ ShortcutSection.NAVIGATION
+ )}
+ href=${this.getChangePath()}
+ >Up</a
+ >
+ <span class="separator"></span>
+ <a
+ class="navLink"
+ title=${this.createTitle(
+ Shortcut.NEXT_FILE,
+ ShortcutSection.NAVIGATION
+ )}
+ href=${ifDefined(this.computeNavLinkURL(1))}
+ >Next</a
+ >
+ </div>`;
+ }
+
+ private renderPatchRangeLeft() {
+ const revisionInfo = this.change
+ ? new RevisionInfoObj(this.change)
+ : undefined;
+ return html` <div class="patchRangeLeft">
+ <gr-patch-range-select
+ id="rangeSelect"
+ .changeNum=${this.changeNum}
+ .patchNum=${this.patchRange?.patchNum}
+ .basePatchNum=${this.patchRange?.basePatchNum}
+ .filesWeblinks=${this.filesWeblinks}
+ .availablePatches=${this.allPatchSets}
+ .revisions=${this.change?.revisions}
+ .revisionInfo=${revisionInfo}
+ @patch-range-change=${this.handlePatchChange}
+ >
+ </gr-patch-range-select>
+ <span class="download desktop">
+ <span class="separator"></span>
+ <gr-dropdown
+ link=""
+ down-arrow=""
+ .items=${this.computeDownloadDropdownLinks()}
+ horizontal-align="left"
+ >
+ <span class="downloadTitle"> Download </span>
+ </gr-dropdown>
+ </span>
+ </div>`;
+ }
+
+ private renderRightControls() {
+ const blameLoaderClass =
+ !isMagicPath(this.path) && !this.isImageDiff ? 'show' : '';
+ const blameToggleLabel =
+ this.isBlameLoaded && !this.isBlameLoading ? 'Hide blame' : 'Show blame';
+ const diffModeSelectorClass = !this.diff || this.diff.binary ? 'hide' : '';
+ return html` <div class="rightControls">
+ <span class="blameLoader ${blameLoaderClass}">
+ <gr-button
+ link=""
+ id="toggleBlame"
+ title=${this.createTitle(
+ Shortcut.TOGGLE_BLAME,
+ ShortcutSection.DIFFS
+ )}
+ ?disabled=${this.isBlameLoading}
+ @click=${this.toggleBlame}
+ >${blameToggleLabel}</gr-button
+ >
+ </span>
+ ${when(
+ this.computeCanEdit(),
+ () => html`
+ <span class="separator"></span>
+ <span class="editButton">
+ <gr-button
+ link=""
+ title="Edit current file"
+ @click=${this.goToEditFile}
+ >edit</gr-button
+ >
+ </span>
+ `
+ )}
+ ${when(
+ this.computeShowEditLinks(),
+ () => html`
+ <span class="separator"></span>
+ ${this.editWeblinks!.map(
+ weblink => html`
+ <a target="_blank" href=${ifDefined(weblink.url)}
+ >${weblink.name}</a
+ >
+ `
+ )}
+ `
+ )}
+ <span class="separator"></span>
+ <div class="diffModeSelector ${diffModeSelectorClass}">
+ <span>Diff view:</span>
+ <gr-diff-mode-selector
+ id="modeSelect"
+ .saveOnChange=${this.loggedIn}
+ show-tooltip-below
+ ></gr-diff-mode-selector>
+ </div>
+ ${when(
+ this.loggedIn && this.prefs,
+ () => html`
+ <span id="diffPrefsContainer">
+ <span class="preferences desktop">
+ <gr-tooltip-content
+ has-tooltip=""
+ position-below=""
+ title="Diff preferences"
+ >
+ <gr-button
+ link=""
+ class="prefsButton"
+ @click=${(e: Event) => this.handlePrefsTap(e)}
+ ><iron-icon icon="gr-icons:settings"></iron-icon
+ ></gr-button>
+ </gr-tooltip-content>
+ </span>
+ </span>
+ `
+ )}
+ <gr-endpoint-decorator name="annotation-toggler">
+ <span hidden="" id="annotation-span">
+ <label for="annotation-checkbox" id="annotation-label"></label>
+ <iron-input>
+ <input
+ is="iron-input"
+ type="checkbox"
+ id="annotation-checkbox"
+ disabled=""
+ />
+ </iron-input>
+ </span>
+ </gr-endpoint-decorator>
+ </div>`;
+ }
+
+ private renderDialogs() {
+ return html` <gr-apply-fix-dialog
+ id="applyFixDialog"
+ .change=${this.change}
+ .changeNum=${this.changeNum}
+ >
+ </gr-apply-fix-dialog>
+ <gr-diff-preferences-dialog
+ id="diffPreferencesDialog"
+ @reload-diff-preference=${this.handleReloadingDiffPreference}
+ >
+ </gr-diff-preferences-dialog>
+ <gr-overlay id="downloadOverlay">
+ <gr-download-dialog
+ id="downloadDialog"
+ .change=${this.change}
+ .patchNum=${this.patchRange?.patchNum}
+ .config=${this.serverConfig?.download}
+ @close=${this.handleDownloadDialogClose}
+ ></gr-download-dialog>
+ </gr-overlay>`;
+ }
+
/**
* Set initial review status of the file.
* automatically mark the file as reviewed if manual review is not set.
*/
-
- async setReviewedStatus(
+ setReviewedStatus(
patchNum: RevisionPatchSetNum,
diffPrefs: DiffPreferencesInfo
) {
- const loggedIn = await this._getLoggedIn();
- if (!loggedIn) return;
+ if (!this.loggedIn) return;
if (!diffPrefs.manual_review) {
this.setReviewed(true, patchNum);
}
}
- @observe('_changeComments', '_path', '_patchRange')
- computeThreads(
- changeComments?: ChangeComments,
- path?: string,
- patchRange?: PatchRange
- ) {
- if (
- changeComments === undefined ||
- path === undefined ||
- patchRange === undefined
- ) {
- return;
- }
- // TODO(dhruvsri): check if basePath should be set here
- this.$.diffHost.threads = changeComments.getThreadsBySideForFile(
- {path},
- patchRange
- );
- }
-
- _getLoggedIn(): Promise<boolean> {
- return this.restApiService.getLoggedIn();
- }
-
- @observe('_change.project')
- _getProjectConfig(project?: RepoName) {
- if (!project) return;
- return this.restApiService.getProjectConfig(project).then(config => {
- this._projectConfig = config;
- });
- }
-
- _getSortedFileList(files?: Files) {
- if (!files) return [];
- return files.sortedFileList;
- }
-
- _getCurrentFile(files?: Files, path?: string) {
- if (!files || !path) return;
- const fileInfo = files.changeFilesByPath[path];
- const fileRange: FileRange = {path};
+ private getFileRange() {
+ if (!this.files || !this.path) return;
+ const fileInfo = this.files.changeFilesByPath[this.path];
+ const fileRange: FileRange = {path: this.path};
if (fileInfo && fileInfo.old_path) {
fileRange.basePath = fileInfo.old_path;
}
return fileRange;
}
- @observe('_changeNum', '_patchRange.*', '_changeComments')
- _getFiles(
- changeNum: NumericChangeId,
- patchRangeRecord: PolymerDeepPropertyChange<PatchRange, PatchRange>,
- changeComments: ChangeComments
- ) {
+ // Private but used in tests.
+ fetchFiles() {
// Polymer 2: check for undefined
- if (
- [changeNum, patchRangeRecord, patchRangeRecord.base, changeComments].some(
- arg => arg === undefined
- )
- ) {
+ if (!this.changeNum || !this.patchRange || !this.changeComments) {
return Promise.resolve();
}
- if (!patchRangeRecord.base.patchNum) {
+ if (!this.patchRange.patchNum) {
return Promise.resolve();
}
- const patchRange = patchRangeRecord.base;
return this.restApiService
- .getChangeFiles(changeNum, patchRange)
+ .getChangeFiles(this.changeNum, this.patchRange)
.then(changeFiles => {
if (!changeFiles) return;
- const commentedPaths = changeComments.getPaths(patchRange);
+ const commentedPaths = this.changeComments!.getPaths(this.patchRange);
const files = {...changeFiles};
addUnmodifiedFiles(files, commentedPaths);
- this._files = {
+ this.files = {
sortedFileList: Object.keys(files).sort(specialFilePathCompare),
changeFilesByPath: files,
};
});
}
- _handleReviewedChange(e: Event) {
- this.setReviewed(
- ((dom(e) as EventApi).rootTarget as HTMLInputElement).checked
- );
+ private handleReviewedChange(e: Event) {
+ const input = e.target as HTMLInputElement;
+ this.setReviewed(input.checked ?? false);
}
// Private but used in tests.
setReviewed(
reviewed: boolean,
- patchNum: RevisionPatchSetNum | undefined = this._patchRange?.patchNum
+ patchNum: RevisionPatchSetNum | undefined = this.patchRange?.patchNum
) {
- if (this._editMode) return;
- if (!patchNum || !this._path || !this._changeNum) return;
- const path = this._path;
+ if (this.computeEditMode()) return;
+ if (!patchNum || !this.path || !this.changeNum) return;
// if file is already reviewed then do not make a saveReview request
- if (this.reviewedFiles.has(path) && reviewed) return;
+ if (this.reviewedFiles.has(this.path) && reviewed) return;
this.getChangeModel().setReviewedFilesStatus(
- this._changeNum,
+ this.changeNum,
patchNum,
- path,
+ this.path,
reviewed
);
}
// Private but used in tests.
handleToggleFileReviewed() {
- this.setReviewed(!this.$.reviewed.checked);
+ assertIsDefined(this.reviewed);
+ this.setReviewed(!this.reviewed.checked);
}
private handlePrevLine() {
- this.$.diffHost.displayLine = true;
+ assertIsDefined(this.diffHost, 'diffHost');
+ this.diffHost.displayLine = true;
this.cursor?.moveUp();
}
private onOpenFixPreview(e: OpenFixPreviewEvent) {
- this.$.applyFixDialog.open(e);
+ assertIsDefined(this.applyFixDialog, 'applyFixDialog');
+ this.applyFixDialog.open(e);
}
- _onIsBlameLoadedchanged(e: ValueChangedEvent<boolean>) {
- this._isBlameLoaded = e.detail.value;
+ private onIsBlameLoadedChanged(e: ValueChangedEvent<boolean>) {
+ this.isBlameLoaded = e.detail.value;
}
- _onDiffChanged(e: ValueChangedEvent<DiffInfo>) {
- this._diff = e.detail.value;
+ private onDiffChanged(e: ValueChangedEvent<DiffInfo>) {
+ this.diff = e.detail.value;
}
- _onEditWeblinksChanged(e: ValueChangedEvent<GeneratedWebLink[] | undefined>) {
- this._editWeblinks = e.detail.value;
+ private onEditWeblinksChanged(
+ e: ValueChangedEvent<GeneratedWebLink[] | undefined>
+ ) {
+ this.editWeblinks = e.detail.value;
}
- _onFilesWeblinksChanged(e: ValueChangedEvent<FilesWebLinks | undefined>) {
- this._filesWeblinks = e.detail.value;
+ private onFilesWeblinksChanged(
+ e: ValueChangedEvent<FilesWebLinks | undefined>
+ ) {
+ this.filesWeblinks = e.detail.value;
}
- _onIsImageDiffChanged(e: ValueChangedEvent<boolean>) {
- this._isImageDiff = e.detail.value;
+ private onIsImageDiffChanged(e: ValueChangedEvent<boolean>) {
+ this.isImageDiff = e.detail.value;
}
private handleNextLine() {
- this.$.diffHost.displayLine = true;
+ assertIsDefined(this.diffHost, 'diffHost');
+ this.diffHost.displayLine = true;
this.cursor?.moveDown();
}
// Private but used in tests.
moveToPreviousFileWithComment() {
- if (!this._commentSkips) return;
- if (!this._change) return;
- if (!this._patchRange?.patchNum) return;
+ if (!this.commentSkips) return;
+ if (!this.change) return;
+ if (!this.patchRange?.patchNum) return;
// If there is no previous diff with comments, then return to the change
// view.
- if (!this._commentSkips.previous) {
+ if (!this.commentSkips.previous) {
this.navToChangeView();
return;
}
GerritNav.navigateToDiff(
- this._change,
- this._commentSkips.previous,
- this._patchRange.patchNum,
- this._patchRange.basePatchNum
+ this.change,
+ this.commentSkips.previous,
+ this.patchRange.patchNum,
+ this.patchRange.basePatchNum
);
}
// Private but used in tests.
moveToNextFileWithComment() {
- if (!this._commentSkips) return;
- if (!this._change) return;
- if (!this._patchRange?.patchNum) return;
+ if (!this.commentSkips) return;
+ if (!this.change) return;
+ if (!this.patchRange?.patchNum) return;
// If there is no next diff with comments, then return to the change view.
- if (!this._commentSkips.next) {
+ if (!this.commentSkips.next) {
this.navToChangeView();
return;
}
GerritNav.navigateToDiff(
- this._change,
- this._commentSkips.next,
- this._patchRange.patchNum,
- this._patchRange.basePatchNum
+ this.change,
+ this.commentSkips.next,
+ this.patchRange.patchNum,
+ this.patchRange.basePatchNum
);
}
@@ -704,15 +1206,15 @@
}
private handlePrevFile() {
- if (!this._path) return;
- if (!this._fileList) return;
- this.navToFile(this._path, this._fileList, -1);
+ if (!this.path) return;
+ if (!this.files?.sortedFileList) return;
+ this.navToFile(this.files.sortedFileList, -1);
}
private handleNextFile() {
- if (!this._path) return;
- if (!this._fileList) return;
- this.navToFile(this._path, this._fileList, 1);
+ if (!this.path) return;
+ if (!this.files?.sortedFileList) return;
+ this.navToFile(this.files.sortedFileList, 1);
}
private handleNextChunk() {
@@ -755,16 +1257,16 @@
}
private navigateToUnreviewedFile(direction: string) {
- if (!this._path) return;
- if (!this._fileList) return;
+ if (!this.path) return;
+ if (!this.files?.sortedFileList) return;
if (!this.reviewedFiles) return;
// Ensure that the currently viewed file always appears in unreviewedFiles
// so we resolve the right "next" file.
- const unreviewedFiles = this._fileList.filter(
- file => file === this._path || !this.reviewedFiles.has(file)
+ const unreviewedFiles = this.files.sortedFileList.filter(
+ file => file === this.path || !this.reviewedFiles.has(file)
);
- this.navToFile(this._path, unreviewedFiles, direction === 'next' ? 1 : -1);
+ this.navToFile(unreviewedFiles, direction === 'next' ? 1 : -1);
}
private handlePrevChunk() {
@@ -780,30 +1282,31 @@
// Similar to gr-change-view.handleOpenReplyDialog
private handleOpenReplyDialog() {
- this._getLoggedIn().then(isLoggedIn => {
- if (!isLoggedIn) {
- fireEvent(this, 'show-auth-required');
- return;
- }
- this.navToChangeView(true);
- });
+ if (!this.loggedIn) {
+ fireEvent(this, 'show-auth-required');
+ return;
+ }
+ this.navToChangeView(true);
}
private handleToggleLeftPane() {
- this.$.diffHost.toggleLeftDiff();
+ assertIsDefined(this.diffHost, 'diffHost');
+ this.diffHost.toggleLeftDiff();
}
private handleOpenDownloadDialog() {
- this.$.downloadOverlay.open().then(() => {
- this.$.downloadOverlay.setFocusStops(
- this.$.downloadDialog.getFocusStops()
- );
- this.$.downloadDialog.focus();
+ assertIsDefined(this.downloadOverlay, 'downloadOverlay');
+ this.downloadOverlay.open().then(() => {
+ assertIsDefined(this.downloadOverlay, 'downloadOverlay');
+ assertIsDefined(this.downloadDialog, 'downloadOverlay');
+ this.downloadOverlay.setFocusStops(this.downloadDialog.getFocusStops());
+ this.downloadDialog.focus();
});
}
- _handleDownloadDialogClose() {
- this.$.downloadOverlay.close();
+ private handleDownloadDialogClose() {
+ assertIsDefined(this.downloadOverlay, 'downloadOverlay');
+ this.downloadOverlay.close();
}
private handleUpToChange() {
@@ -811,14 +1314,15 @@
}
private handleCommaKey() {
- if (!this._loggedIn) return;
- this.$.diffPreferencesDialog.open();
+ if (!this.loggedIn) return;
+ assertIsDefined(this.diffPreferencesDialog, 'diffPreferencesDialog');
+ this.diffPreferencesDialog.open();
}
// Private but used in tests.
handleToggleDiffMode() {
- if (!this._userPrefs) return;
- if (this._userPrefs.diff_view === DiffViewMode.SIDE_BY_SIDE) {
+ if (!this.userPrefs) return;
+ if (this.userPrefs.diff_view === DiffViewMode.SIDE_BY_SIDE) {
this.userModel.updatePreferences({diff_view: DiffViewMode.UNIFIED});
} else {
this.userModel.updatePreferences({
@@ -829,34 +1333,33 @@
// Private but used in tests.
navToChangeView(openReplyDialog = false) {
- if (!this._changeNum || !this._patchRange?.patchNum) {
+ if (!this.changeNum || !this.patchRange?.patchNum) {
return;
}
- this._navigateToChange(
- this._change,
- this._patchRange,
- this._change && this._change.revisions,
+ this.navigateToChange(
+ this.change,
+ this.patchRange,
+ this.change && this.change.revisions,
openReplyDialog
);
}
// Private but used in tests.
navToFile(
- path: string,
fileList: string[],
direction: -1 | 1,
navigateToFirstComment?: boolean
) {
- const newPath = this.getNavLinkPath(path, fileList, direction);
+ const newPath = this.getNavLinkPath(fileList, direction);
if (!newPath) return;
- if (!this._change) return;
- if (!this._patchRange) return;
+ if (!this.change) return;
+ if (!this.patchRange) return;
if (newPath.up) {
- this._navigateToChange(
- this._change,
- this._patchRange,
- this._change && this._change.revisions
+ this.navigateToChange(
+ this.change,
+ this.patchRange,
+ this.change && this.change.revisions
);
return;
}
@@ -864,64 +1367,52 @@
if (!newPath.path) return;
let lineNum;
if (navigateToFirstComment)
- lineNum = this._changeComments?.getCommentsForPath(
+ lineNum = this.changeComments?.getCommentsForPath(
newPath.path,
- this._patchRange
+ this.patchRange
)?.[0].line;
GerritNav.navigateToDiff(
- this._change,
+ this.change,
newPath.path,
- this._patchRange.patchNum,
- this._patchRange.basePatchNum,
+ this.patchRange.patchNum,
+ this.patchRange.basePatchNum,
lineNum
);
}
/**
- * @param path The path of the current file being shown.
- * @param fileList The list of files in this change and
- * patch range.
* @param direction Either 1 (next file) or -1 (prev file).
* @return The next URL when proceeding in the specified
* direction.
*/
- _computeNavLinkURL(
- change?: ChangeInfo,
- path?: string,
- fileList?: string[],
- direction?: -1 | 1
- ) {
- if (!change) return null;
- if (!path) return null;
- if (!fileList) return null;
- if (!direction) return null;
+ private computeNavLinkURL(direction?: -1 | 1) {
+ if (!this.change) return;
+ if (!this.path) return;
+ if (!this.files?.sortedFileList) return;
+ if (!direction) return;
- const newPath = this.getNavLinkPath(path, fileList, direction);
+ const newPath = this.getNavLinkPath(this.files.sortedFileList, direction);
if (!newPath) {
- return null;
+ return;
}
if (newPath.up) {
- return this._getChangePath(
- this._change,
- this._patchRange,
- this._change && this._change.revisions
- );
+ return this.getChangePath();
}
- return this._getDiffUrl(this._change, this._patchRange, newPath.path);
+ return this.getDiffUrl(this.change, this.patchRange, newPath.path);
}
- _goToEditFile() {
- if (!this._change) return;
- if (!this._path) return;
- if (!this._patchRange) return;
+ private goToEditFile() {
+ if (!this.change) return;
+ if (!this.path) return;
+ if (!this.patchRange) return;
// TODO(taoalpha): add a shortcut for editing
const cursorAddress = this.cursor?.getAddress();
const editUrl = GerritNav.getEditUrlForDiff(
- this._change,
- this._path,
- this._patchRange.patchNum,
+ this.change,
+ this.path,
+ this.patchRange.patchNum,
cursorAddress?.number
);
GerritNav.navigateToRelativeUrl(editUrl);
@@ -942,12 +1433,12 @@
* patch range.
* @param direction Either 1 (next file) or -1 (prev file).
*/
- private getNavLinkPath(path: string, fileList: string[], direction: -1 | 1) {
- if (!path || !fileList || fileList.length === 0) {
+ private getNavLinkPath(fileList: string[], direction: -1 | 1) {
+ if (!this.path || !fileList || fileList.length === 0) {
return null;
}
- let idx = fileList.indexOf(path);
+ let idx = fileList.indexOf(this.path);
if (idx === -1) {
const file = direction > 0 ? fileList[0] : fileList[fileList.length - 1];
return {path: file};
@@ -965,43 +1456,44 @@
// Private but used in tests.
initLineOfInterestAndCursor(leftSide: boolean) {
- this.$.diffHost.lineOfInterest = this._getLineOfInterest(leftSide);
+ assertIsDefined(this.diffHost, 'diffHost');
+ this.diffHost.lineOfInterest = this.getLineOfInterest(leftSide);
this.initCursor(leftSide);
}
// Private but used in tests.
displayDiffBaseAgainstLeftToast() {
- if (!this._patchRange) return;
+ if (!this.patchRange) return;
fireAlert(
this,
- `Patchset ${this._patchRange.basePatchNum} vs ` +
- `${this._patchRange.patchNum} selected. Press v + \u2190 to view ` +
- `Base vs ${this._patchRange.basePatchNum}`
+ `Patchset ${this.patchRange.basePatchNum} vs ` +
+ `${this.patchRange.patchNum} selected. Press v + \u2190 to view ` +
+ `Base vs ${this.patchRange.basePatchNum}`
);
}
private displayDiffAgainstLatestToast(latestPatchNum?: PatchSetNum) {
- if (!this._patchRange) return;
+ if (!this.patchRange) return;
const leftPatchset =
- this._patchRange.basePatchNum === PARENT
+ this.patchRange.basePatchNum === PARENT
? 'Base'
- : `Patchset ${this._patchRange.basePatchNum}`;
+ : `Patchset ${this.patchRange.basePatchNum}`;
fireAlert(
this,
`${leftPatchset} vs
- ${this._patchRange.patchNum} selected\n. Press v + \u2191 to view
+ ${this.patchRange.patchNum} selected\n. Press v + \u2191 to view
${leftPatchset} vs Patchset ${latestPatchNum}`
);
}
private displayToasts() {
- if (!this._patchRange) return;
- if (this._patchRange.basePatchNum !== PARENT) {
+ if (!this.patchRange) return;
+ if (this.patchRange.basePatchNum !== PARENT) {
this.displayDiffBaseAgainstLeftToast();
return;
}
- const latestPatchNum = computeLatestPatchNum(this._allPatchSets);
- if (this._patchRange.patchNum !== latestPatchNum) {
+ const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
+ if (this.patchRange.patchNum !== latestPatchNum) {
this.displayDiffAgainstLatestToast(latestPatchNum);
return;
}
@@ -1010,36 +1502,36 @@
private initCommitRange() {
let commit: CommitId | undefined;
let baseCommit: CommitId | undefined;
- if (!this._change) return;
- if (!this._patchRange || !this._patchRange.patchNum) return;
- const revisions = this._change.revisions ?? {};
+ if (!this.change) return;
+ if (!this.patchRange || !this.patchRange.patchNum) return;
+ const revisions = this.change.revisions ?? {};
for (const [commitSha, revision] of Object.entries(revisions)) {
const patchNum = revision._number;
- if (patchNum === this._patchRange.patchNum) {
+ if (patchNum === this.patchRange.patchNum) {
commit = commitSha as CommitId;
const commitObj = revision.commit;
const parents = commitObj?.parents || [];
- if (this._patchRange.basePatchNum === PARENT && parents.length) {
+ if (this.patchRange.basePatchNum === PARENT && parents.length) {
baseCommit = parents[parents.length - 1].commit;
}
- } else if (patchNum === this._patchRange.basePatchNum) {
+ } else if (patchNum === this.patchRange.basePatchNum) {
baseCommit = commitSha as CommitId;
}
}
- this._commitRange = commit && baseCommit ? {commit, baseCommit} : undefined;
+ this.commitRange = commit && baseCommit ? {commit, baseCommit} : undefined;
}
private updateUrlToDiffUrl(lineNum?: number, leftSide?: boolean) {
- if (!this._change) return;
- if (!this._patchRange) return;
- if (!this._changeNum) return;
- if (!this._path) return;
+ if (!this.change) return;
+ if (!this.patchRange) return;
+ if (!this.changeNum) return;
+ if (!this.path) return;
const url = GerritNav.getUrlForDiffById(
- this._changeNum,
- this._change.project,
- this._path,
- this._patchRange.patchNum,
- this._patchRange.basePatchNum,
+ this.changeNum,
+ this.change.project,
+ this.path,
+ this.patchRange.patchNum,
+ this.patchRange.basePatchNum,
lineNum,
leftSide
);
@@ -1049,49 +1541,49 @@
// Private but used in tests.
initPatchRange() {
let leftSide = false;
- if (!this._change) return;
+ if (!this.change) return;
if (this.params?.view !== GerritView.DIFF) return;
if (this.params?.commentId) {
- const comment = this._changeComments?.findCommentById(
+ const comment = this.changeComments?.findCommentById(
this.params.commentId
);
if (!comment) {
fireAlert(this, 'comment not found');
- GerritNav.navigateToChange(this._change);
+ GerritNav.navigateToChange(this.change);
return;
}
this.getChangeModel().updatePath(comment.path);
- const latestPatchNum = computeLatestPatchNum(this._allPatchSets);
- if (!latestPatchNum) throw new Error('Missing _allPatchSets');
- this._patchRange = getPatchRangeForCommentUrl(comment, latestPatchNum);
- leftSide = isInBaseOfPatchRange(comment, this._patchRange);
+ const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
+ if (!latestPatchNum) throw new Error('Missing allPatchSets');
+ this.patchRange = getPatchRangeForCommentUrl(comment, latestPatchNum);
+ leftSide = isInBaseOfPatchRange(comment, this.patchRange);
- this._focusLineNum = comment.line;
+ this.focusLineNum = comment.line;
} else {
if (this.params.path) {
this.getChangeModel().updatePath(this.params.path);
}
if (this.params.patchNum) {
- this._patchRange = {
+ this.patchRange = {
patchNum: this.params.patchNum,
basePatchNum: this.params.basePatchNum || PARENT,
};
}
if (this.params.lineNum) {
- this._focusLineNum = this.params.lineNum;
+ this.focusLineNum = this.params.lineNum;
leftSide = !!this.params.leftSide;
}
}
- assertIsDefined(this._patchRange, '_patchRange');
+ assertIsDefined(this.patchRange, 'patchRange');
this.initLineOfInterestAndCursor(leftSide);
if (this.params?.commentId) {
// url is of type /comment/{commentId} which isn't meaningful
- this.updateUrlToDiffUrl(this._focusLineNum, leftSide);
+ this.updateUrlToDiffUrl(this.focusLineNum, leftSide);
}
- this._commentMap = this._getPaths(this._patchRange);
+ this.commentMap = this.getPaths();
}
// Private but used in tests.
@@ -1105,9 +1597,9 @@
private isSameDiffLoaded(value: AppElementDiffViewParam) {
return (
- this._patchRange?.basePatchNum === value.basePatchNum &&
- this._patchRange?.patchNum === value.patchNum &&
- this._path === value.path
+ this.patchRange?.basePatchNum === value.basePatchNum &&
+ this.patchRange?.patchNum === value.patchNum &&
+ this.path === value.path
);
}
@@ -1126,11 +1618,13 @@
}
// Private but used in tests.
- _paramsChanged(value: AppElementParams) {
- if (value.view !== GerritView.DIFF) {
+ paramsChanged() {
+ if (this.params?.view !== GerritView.DIFF) {
return;
}
+ const params = this.params;
+
// The diff view is kept in the background once created. If the user
// scrolls in the change page, the scrolling is reflected in the diff view
// as well, which means the diff is scrolled to a random position based
@@ -1140,11 +1634,11 @@
// Everything in the diff view is tied to the change. It seems better to
// force the re-creation of the diff view when the change number changes.
- const changeChanged = this._changeNum !== value.changeNum;
- if (this._changeNum !== undefined && changeChanged) {
+ const changeChanged = this.changeNum !== params.changeNum;
+ if (this.changeNum !== undefined && changeChanged) {
fireEvent(this, EventType.RECREATE_DIFF_VIEW);
return;
- } else if (this._changeNum !== undefined && this.isSameDiffLoaded(value)) {
+ } else if (this.changeNum !== undefined && this.isSameDiffLoaded(params)) {
// changeNum has not changed, so check if there are changes in patchRange
// path. If no changes then we can simply render the view as is.
this.reporting.reportInteraction('diff-view-re-rendered');
@@ -1155,59 +1649,61 @@
return;
}
- this._files = {sortedFileList: [], changeFilesByPath: {}};
+ this.files = {sortedFileList: [], changeFilesByPath: {}};
if (this.isConnected) {
this.getChangeModel().updatePath(undefined);
}
- this._patchRange = undefined;
- this._commitRange = undefined;
- this._focusLineNum = undefined;
+ this.patchRange = undefined;
+ this.commitRange = undefined;
+ this.focusLineNum = undefined;
- if (value.changeNum && value.project) {
- this.restApiService.setInProjectLookup(value.changeNum, value.project);
+ if (params.changeNum && params.project) {
+ this.restApiService.setInProjectLookup(params.changeNum, params.project);
}
- this._changeNum = value.changeNum;
+ this.changeNum = params.changeNum;
this.classList.remove('hideComments');
// When navigating away from the page, there is a possibility that the
// patch number is no longer a part of the URL (say when navigating to
// the top-level change info view) and therefore undefined in `params`.
// If route is of type /comment/<commentId>/ then no patchNum is present
- if (!value.patchNum && !value.commentLink) {
+ if (!params.patchNum && !params.commentLink) {
this.reporting.error(
- new Error(`Invalid diff view URL, no patchNum found: ${value}`)
+ new Error(`Invalid diff view URL, no patchNum found: ${this.params}`)
);
return;
}
const promises: Promise<unknown>[] = [];
- if (!this._change) {
+ if (!this.change) {
promises.push(this.untilModelLoaded());
}
promises.push(this.waitUntilCommentsLoaded());
- this.$.diffHost.cancel();
- this.$.diffHost.clearDiffContent();
- this._loading = true;
+ if (this.diffHost) {
+ this.diffHost.cancel();
+ this.diffHost.clearDiffContent();
+ }
+ this.loading = true;
return Promise.all(promises)
.then(() => {
- this._loading = false;
+ this.loading = false;
this.initPatchRange();
this.initCommitRange();
- return this.$.diffHost.reload(true);
+ return this.updateComplete.then(() => this.diffHost!.reload(true));
})
.then(() => {
this.reporting.diffViewDisplayed();
})
.then(() => {
- const fileUnchanged = this.isFileUnchanged(this._diff);
- if (fileUnchanged && value.commentLink) {
- assertIsDefined(this._change, '_change');
- assertIsDefined(this._path, '_path');
- assertIsDefined(this._patchRange, '_patchRange');
+ const fileUnchanged = this.isFileUnchanged(this.diff);
+ if (fileUnchanged && params.commentLink) {
+ assertIsDefined(this.change, 'change');
+ assertIsDefined(this.path, 'path');
+ assertIsDefined(this.patchRange, 'patchRange');
- if (this._patchRange.basePatchNum === PARENT) {
+ if (this.patchRange.basePatchNum === PARENT) {
// file is unchanged between Base vs X
// hence should not show diff between Base vs Base
return;
@@ -1216,25 +1712,25 @@
fireAlert(
this,
`File is unchanged between Patchset
- ${this._patchRange.basePatchNum} and
- ${this._patchRange.patchNum}. Showing diff of Base vs
- ${this._patchRange.basePatchNum}`
+ ${this.patchRange.basePatchNum} and
+ ${this.patchRange.patchNum}. Showing diff of Base vs
+ ${this.patchRange.basePatchNum}`
);
GerritNav.navigateToDiff(
- this._change,
- this._path,
- this._patchRange.basePatchNum as RevisionPatchSetNum,
+ this.change,
+ this.path,
+ this.patchRange.basePatchNum as RevisionPatchSetNum,
PARENT,
- this._focusLineNum
+ this.focusLineNum
);
return;
}
- if (value.commentLink) {
+ if (params.commentLink) {
this.displayToasts();
}
// If the blame was loaded for a previous file and user navigates to
// another file, then we load the blame for this file too
- if (this._isBlameLoaded) this.loadBlame();
+ if (this.isBlameLoaded) this.loadBlame();
});
}
@@ -1248,7 +1744,7 @@
* Private but used in tests.
*/
initCursor(leftSide: boolean) {
- if (this._focusLineNum === undefined) {
+ if (this.focusLineNum === undefined) {
return;
}
if (!this.cursor) return;
@@ -1257,29 +1753,30 @@
} else {
this.cursor.side = Side.RIGHT;
}
- this.cursor.initialLineNumber = this._focusLineNum;
+ this.cursor.initialLineNumber = this.focusLineNum;
}
- _getLineOfInterest(leftSide: boolean): DisplayLine | undefined {
+ // Private but used in tests.
+ getLineOfInterest(leftSide: boolean): DisplayLine | undefined {
// If there is a line number specified, pass it along to the diff so that
// it will not get collapsed.
- if (!this._focusLineNum) {
+ if (!this.focusLineNum) {
return undefined;
}
return {
- lineNum: this._focusLineNum,
+ lineNum: this.focusLineNum,
side: leftSide ? Side.LEFT : Side.RIGHT,
};
}
- _pathChanged(path: string) {
- if (path) {
- fireTitleChange(this, computeTruncatedPath(path));
+ private pathChanged() {
+ if (this.path) {
+ fireTitleChange(this, computeTruncatedPath(this.path));
}
}
- _getDiffUrl(
+ private getDiffUrl(
change?: ChangeInfo | ParsedChangeInfo,
patchRange?: PatchRange,
path?: string
@@ -1298,7 +1795,7 @@
* patch) then the patch range need not appear in the URL. Return a patch
* range object with undefined values when a range is not needed.
*/
- _getChangeUrlRange(
+ private getChangeUrlRange(
patchRange?: PatchRange,
revisions?: {[revisionId: string]: RevisionInfo | EditRevisionInfo}
) {
@@ -1321,29 +1818,29 @@
return {patchNum, basePatchNum};
}
- _getChangePath(
- change?: ChangeInfo | ParsedChangeInfo,
- patchRange?: PatchRange,
- revisions?: {[revisionId: string]: RevisionInfo | EditRevisionInfo}
- ) {
- if (!change) return '';
- if (!patchRange) return '';
+ private getChangePath() {
+ if (!this.change) return '';
+ if (!this.patchRange) return '';
- const range = this._getChangeUrlRange(patchRange, revisions);
- return GerritNav.getUrlForChange(change, {
+ const range = this.getChangeUrlRange(
+ this.patchRange,
+ this.change.revisions
+ );
+ return GerritNav.getUrlForChange(this.change, {
patchNum: range.patchNum,
basePatchNum: range.basePatchNum,
});
}
- _navigateToChange(
+ // Private but used in tests.
+ navigateToChange(
change?: ChangeInfo | ParsedChangeInfo,
patchRange?: PatchRange,
revisions?: {[revisionId: string]: RevisionInfo | EditRevisionInfo},
openReplyDialog?: boolean
) {
if (!change) return;
- const range = this._getChangeUrlRange(patchRange, revisions);
+ const range = this.getChangeUrlRange(patchRange, revisions);
GerritNav.navigateToChange(change, {
patchNum: range.patchNum,
basePatchNum: range.basePatchNum,
@@ -1351,134 +1848,111 @@
});
}
- _computeChangePath(
- change?: ChangeInfo,
- patchRangeRecord?: PolymerDeepPropertyChange<PatchRange, PatchRange>,
- revisions?: {[revisionId: string]: RevisionInfo}
- ) {
- if (!patchRangeRecord) return '';
- return this._getChangePath(change, patchRangeRecord.base, revisions);
- }
-
- _formatFilesForDropdown(
- files?: Files,
- patchRange?: PatchRange,
- changeComments?: ChangeComments
- ): DropdownItem[] {
- if (!files) return [];
- if (!patchRange) return [];
- if (!changeComments) return [];
+ // Private but used in tests
+ formatFilesForDropdown(): DropdownItem[] {
+ if (!this.files) return [];
+ if (!this.patchRange) return [];
+ if (!this.changeComments) return [];
const dropdownContent: DropdownItem[] = [];
- for (const path of files.sortedFileList) {
+ for (const path of this.files.sortedFileList) {
dropdownContent.push({
text: computeDisplayPath(path),
mobileText: computeTruncatedPath(path),
value: path,
- bottomText: changeComments.computeCommentsString(
- patchRange,
+ bottomText: this.changeComments.computeCommentsString(
+ this.patchRange,
path,
- files.changeFilesByPath[path],
+ this.files.changeFilesByPath[path],
/* includeUnmodified= */ true
),
- file: {...files.changeFilesByPath[path], __path: path},
+ file: {...this.files.changeFilesByPath[path], __path: path},
});
}
return dropdownContent;
}
- _computePrefsButtonHidden(prefs?: DiffPreferencesInfo, loggedIn?: boolean) {
- return !loggedIn || !prefs;
- }
-
- _handleFileChange(e: CustomEvent) {
- if (!this._change) return;
- if (!this._patchRange) return;
+ // Private but used in tests.
+ handleFileChange(e: CustomEvent) {
+ if (!this.change) return;
+ if (!this.patchRange) return;
// This is when it gets set initially.
const path = e.detail.value;
- if (path === this._path) {
+ if (path === this.path) {
return;
}
GerritNav.navigateToDiff(
- this._change,
+ this.change,
path,
- this._patchRange.patchNum,
- this._patchRange.basePatchNum
+ this.patchRange.patchNum,
+ this.patchRange.basePatchNum
);
}
- _handlePatchChange(e: CustomEvent) {
- if (!this._change) return;
- if (!this._path) return;
- if (!this._patchRange) return;
+ // Private but used in tests.
+ handlePatchChange(e: CustomEvent) {
+ if (!this.change) return;
+ if (!this.path) return;
+ if (!this.patchRange) return;
const {basePatchNum, patchNum} = e.detail;
if (
- basePatchNum === this._patchRange.basePatchNum &&
- patchNum === this._patchRange.patchNum
+ basePatchNum === this.patchRange.basePatchNum &&
+ patchNum === this.patchRange.patchNum
) {
return;
}
- GerritNav.navigateToDiff(this._change, this._path, patchNum, basePatchNum);
+ GerritNav.navigateToDiff(this.change, this.path, patchNum, basePatchNum);
}
- _handlePrefsTap(e: Event) {
+ // Private but used in tests.
+ handlePrefsTap(e: Event) {
e.preventDefault();
- this.$.diffPreferencesDialog.open();
+ assertIsDefined(this.diffPreferencesDialog, 'diffPreferencesDialog');
+ this.diffPreferencesDialog.open();
}
- _computeModeSelectHideClass(diff?: DiffInfo) {
- return !diff || diff.binary ? 'hide' : '';
- }
-
- _onLineSelected(
- _: Event,
- detail: {side: Side | CommentSide; number: number}
- ) {
+ // Private but used in tests.
+ onLineSelected(e: CustomEvent) {
// for on-comment-anchor-tap side can be PARENT/REVISIONS
// for on-line-selected side can be left/right
this.updateUrlToDiffUrl(
- detail.number,
- detail.side === Side.LEFT || detail.side === CommentSide.PARENT
+ e.detail.number,
+ e.detail.side === Side.LEFT || e.detail.side === CommentSide.PARENT
);
}
- _computeDownloadDropdownLinks(
- project?: RepoName,
- changeNum?: NumericChangeId,
- patchRange?: PatchRange,
- path?: string,
- diff?: DiffInfo
- ) {
- if (!project) return [];
- if (!changeNum) return [];
- if (!patchRange || !patchRange.patchNum) return [];
- if (!path) return [];
+ // Private but used in tests.
+ computeDownloadDropdownLinks() {
+ if (!this.change?.project) return [];
+ if (!this.changeNum) return [];
+ if (!this.patchRange?.patchNum) return [];
+ if (!this.path) return [];
const links = [
{
- url: this._computeDownloadPatchLink(
- project,
- changeNum,
- patchRange,
- path
+ url: this.computeDownloadPatchLink(
+ this.change.project,
+ this.changeNum,
+ this.patchRange,
+ this.path
),
name: 'Patch',
},
];
- if (diff && diff.meta_a) {
- let leftPath = path;
- if (diff.change_type === 'RENAMED') {
- leftPath = diff.meta_a.name;
+ if (this.diff && this.diff.meta_a) {
+ let leftPath = this.path;
+ if (this.diff.change_type === 'RENAMED') {
+ leftPath = this.diff.meta_a.name;
}
links.push({
- url: this._computeDownloadFileLink(
- project,
- changeNum,
- patchRange,
+ url: this.computeDownloadFileLink(
+ this.change.project,
+ this.changeNum,
+ this.patchRange,
leftPath,
true
),
@@ -1486,13 +1960,13 @@
});
}
- if (diff && diff.meta_b) {
+ if (this.diff && this.diff.meta_b) {
links.push({
- url: this._computeDownloadFileLink(
- project,
- changeNum,
- patchRange,
- path,
+ url: this.computeDownloadFileLink(
+ this.change.project,
+ this.changeNum,
+ this.patchRange,
+ this.path,
false
),
name: 'Right Content',
@@ -1502,7 +1976,8 @@
return links;
}
- _computeDownloadFileLink(
+ // Private but used in tests.
+ computeDownloadFileLink(
project: RepoName,
changeNum: NumericChangeId,
patchRange: PatchRange,
@@ -1529,7 +2004,8 @@
return url;
}
- _computeDownloadPatchLink(
+ // Private but used in tests.
+ computeDownloadPatchLink(
project: RepoName,
changeNum: NumericChangeId,
patchRange: PatchRange,
@@ -1540,41 +2016,14 @@
return url;
}
- @observe(
- '_changeComments',
- '_files.changeFilesByPath',
- '_path',
- '_patchRange',
- '_projectConfig'
- )
- _recomputeComments(
- changeComments?: ChangeComments,
- files?: {[path: string]: FileInfo},
- path?: string,
- patchRange?: PatchRange,
- projectConfig?: ConfigInfo
- ) {
- if (!files) return;
- if (!path) return;
- if (!patchRange) return;
- if (!projectConfig) return;
- if (!changeComments) return;
-
- const file = files[path];
- if (file && file.old_path) {
- this.$.diffHost.threads = changeComments.getThreadsBySideForFile(
- {path, basePath: file.old_path},
- patchRange
- );
- }
+ // Private but used in tests.
+ getPaths(): CommentMap {
+ if (!this.changeComments) return {};
+ return this.changeComments.getPaths(this.patchRange);
}
- _getPaths(patchRange: PatchRange): CommentMap {
- if (!this._changeComments) return {};
- return this._changeComments.getPaths(patchRange);
- }
-
- _computeCommentSkips(
+ // Private but used in tests.
+ computeCommentSkips(
commentMap?: CommentMap,
fileList?: string[],
path?: string
@@ -1608,33 +2057,24 @@
return skips;
}
- _computeContainerClass(editMode: boolean) {
- return editMode ? 'editMode' : '';
- }
-
- _computeEditMode(
- patchRangeRecord: PolymerDeepPropertyChange<PatchRange, PatchRange>
- ) {
- const patchRange = patchRangeRecord.base || {};
- return patchRange.patchNum === EDIT;
- }
-
- _computeBlameToggleLabel(loaded?: boolean, loading?: boolean) {
- return loaded && !loading ? 'Hide blame' : 'Show blame';
+ // Private but used in tests.
+ computeEditMode() {
+ return this.patchRange?.patchNum === EDIT;
}
// Private but used in tests.
loadBlame() {
- this._isBlameLoading = true;
+ this.isBlameLoading = true;
fireAlert(this, LOADING_BLAME);
- this.$.diffHost
+ assertIsDefined(this.diffHost, 'diffHost');
+ this.diffHost
.loadBlame()
.then(() => {
- this._isBlameLoading = false;
+ this.isBlameLoading = false;
fireAlert(this, LOADED_BLAME);
})
.catch(() => {
- this._isBlameLoading = false;
+ this.isBlameLoading = false;
});
}
@@ -1642,135 +2082,123 @@
* Load and display blame information if it has not already been loaded.
* Otherwise hide it.
*/
- _toggleBlame() {
- if (this._isBlameLoaded) {
- this.$.diffHost.clearBlame();
+ private toggleBlame() {
+ assertIsDefined(this.diffHost, 'diffHost');
+ if (this.isBlameLoaded) {
+ this.diffHost.clearBlame();
return;
}
this.loadBlame();
}
- private handleToggleBlame() {
- this._toggleBlame();
- }
-
- _handleToggleHideAllCommentThreads() {
+ private handleToggleHideAllCommentThreads() {
toggleClass(this, 'hideComments');
}
private handleOpenFileList() {
- this.$.dropdown.open();
+ assertIsDefined(this.dropdown, 'dropdown');
+ this.dropdown.open();
}
// Private but used in tests.
handleDiffAgainstBase() {
- if (!this._change) return;
- if (!this._path) return;
- if (!this._patchRange) return;
+ if (!this.change) return;
+ if (!this.path) return;
+ if (!this.patchRange) return;
- if (this._patchRange.basePatchNum === PARENT) {
+ if (this.patchRange.basePatchNum === PARENT) {
fireAlert(this, 'Base is already selected.');
return;
}
- GerritNav.navigateToDiff(
- this._change,
- this._path,
- this._patchRange.patchNum
- );
+ GerritNav.navigateToDiff(this.change, this.path, this.patchRange.patchNum);
}
// Private but used in tests.
handleDiffBaseAgainstLeft() {
- if (!this._change) return;
- if (!this._path) return;
- if (!this._patchRange) return;
+ if (!this.change) return;
+ if (!this.path) return;
+ if (!this.patchRange) return;
- if (this._patchRange.basePatchNum === PARENT) {
+ if (this.patchRange.basePatchNum === PARENT) {
fireAlert(this, 'Left is already base.');
return;
}
GerritNav.navigateToDiff(
- this._change,
- this._path,
- this._patchRange.basePatchNum as RevisionPatchSetNum,
+ this.change,
+ this.path,
+ this.patchRange.basePatchNum as RevisionPatchSetNum,
PARENT,
this.params?.view === GerritView.DIFF && this.params?.commentLink
- ? this._focusLineNum
+ ? this.focusLineNum
: undefined
);
}
// Private but used in tests.
handleDiffAgainstLatest() {
- if (!this._change) return;
- if (!this._path) return;
- if (!this._patchRange) return;
+ if (!this.change) return;
+ if (!this.path) return;
+ if (!this.patchRange) return;
- const latestPatchNum = computeLatestPatchNum(this._allPatchSets);
- if (this._patchRange.patchNum === latestPatchNum) {
+ const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
+ if (this.patchRange.patchNum === latestPatchNum) {
fireAlert(this, 'Latest is already selected.');
return;
}
GerritNav.navigateToDiff(
- this._change,
- this._path,
+ this.change,
+ this.path,
latestPatchNum,
- this._patchRange.basePatchNum
+ this.patchRange.basePatchNum
);
}
// Private but used in tests.
handleDiffRightAgainstLatest() {
- if (!this._change) return;
- if (!this._path) return;
- if (!this._patchRange) return;
+ if (!this.change) return;
+ if (!this.path) return;
+ if (!this.patchRange) return;
- const latestPatchNum = computeLatestPatchNum(this._allPatchSets);
- if (this._patchRange.patchNum === latestPatchNum) {
+ const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
+ if (this.patchRange.patchNum === latestPatchNum) {
fireAlert(this, 'Right is already latest.');
return;
}
GerritNav.navigateToDiff(
- this._change,
- this._path,
+ this.change,
+ this.path,
latestPatchNum,
- this._patchRange.patchNum as BasePatchSetNum
+ this.patchRange.patchNum as BasePatchSetNum
);
}
// Private but used in tests.
handleDiffBaseAgainstLatest() {
- if (!this._change) return;
- if (!this._path) return;
- if (!this._patchRange) return;
+ if (!this.change) return;
+ if (!this.path) return;
+ if (!this.patchRange) return;
- const latestPatchNum = computeLatestPatchNum(this._allPatchSets);
+ const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
if (
- this._patchRange.patchNum === latestPatchNum &&
- this._patchRange.basePatchNum === PARENT
+ this.patchRange.patchNum === latestPatchNum &&
+ this.patchRange.basePatchNum === PARENT
) {
fireAlert(this, 'Already diffing base against latest.');
return;
}
- GerritNav.navigateToDiff(this._change, this._path, latestPatchNum);
+ GerritNav.navigateToDiff(this.change, this.path, latestPatchNum);
}
- _computeBlameLoaderClass(isImageDiff?: boolean, path?: string) {
- return !isMagicPath(path) && !isImageDiff ? 'show' : '';
+ // Private but used in tests.
+ computeFileNum(files: DropdownItem[]) {
+ if (!this.path || !files) return undefined;
+
+ return files.findIndex(({value}) => value === this.path) + 1;
}
- _getRevisionInfo(change: ChangeInfo) {
- return new RevisionInfoObj(change);
- }
-
- _computeFileNum(file?: string, files?: DropdownItem[]) {
- if (!file || !files) return undefined;
-
- return files.findIndex(({value}) => value === file) + 1;
- }
-
- _computeFileNumClass(fileNum?: number, files?: DropdownItem[]) {
+ // Private but used in tests.
+ computeFileNumClass(fileNum?: number, files?: DropdownItem[]) {
if (files && fileNum && fileNum > 0) {
return 'show';
}
@@ -1778,7 +2206,8 @@
}
private handleToggleAllDiffContext() {
- this.$.diffHost.toggleAllContext();
+ assertIsDefined(this.diffHost, 'diffHost');
+ this.diffHost.toggleAllContext();
}
private handleNextUnreviewedFile() {
@@ -1787,59 +2216,34 @@
}
private navigateToNextFileWithCommentThread() {
- if (!this._path) return;
- if (!this._fileList) return;
- if (!this._patchRange) return;
- if (!this._change) return;
+ if (!this.path) return;
+ if (!this.files?.sortedFileList) return;
+ if (!this.patchRange) return;
+ if (!this.change) return;
const hasComment = (path: string) =>
- this._changeComments?.getCommentsForPath(path, this._patchRange!)
- ?.length ?? 0 > 0;
- const filesWithComments = this._fileList.filter(
- file => file === this._path || hasComment(file)
+ this.changeComments?.getCommentsForPath(path, this.patchRange!)?.length ??
+ 0 > 0;
+ const filesWithComments = this.files.sortedFileList.filter(
+ file => file === this.path || hasComment(file)
);
- this.navToFile(this._path, filesWithComments, 1, true);
+ this.navToFile(filesWithComments, 1, true);
}
- _handleReloadingDiffPreference() {
+ private handleReloadingDiffPreference() {
this.userModel.getDiffPreferences();
}
- _computeCanEdit(
- loggedIn?: boolean,
- editWeblinks?: GeneratedWebLink[],
- changeChangeRecord?: PolymerDeepPropertyChange<ChangeInfo, ChangeInfo>
- ) {
- if (!changeChangeRecord?.base) return false;
+ private computeCanEdit() {
return (
- loggedIn &&
- changeIsOpen(changeChangeRecord.base) &&
- (!editWeblinks || editWeblinks.length === 0)
+ !!this.change &&
+ !!this.loggedIn &&
+ changeIsOpen(this.change) &&
+ !this.computeShowEditLinks()
);
}
- _computeShowEditLinks(editWeblinks?: GeneratedWebLink[]) {
- return !!editWeblinks && editWeblinks.length > 0;
- }
-
- /**
- * Wrapper for using in the element template and computed properties
- */
- _computeAllPatchSets(change: ChangeInfo) {
- return computeAllPatchSets(change);
- }
-
- /**
- * Wrapper for using in the element template and computed properties
- */
- _computeDisplayPath(path: string) {
- return computeDisplayPath(path);
- }
-
- /**
- * Wrapper for using in the element template and computed properties
- */
- _computeTruncatedPath(path?: string) {
- return path ? computeTruncatedPath(path) : '';
+ private computeShowEditLinks() {
+ return !!this.editWeblinks && this.editWeblinks.length > 0;
}
createTitle(shortcutName: Shortcut, section: ShortcutSection) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
deleted file mode 100644
index 04d04ff..0000000
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
+++ /dev/null
@@ -1,421 +0,0 @@
-/**
- * @license
- * Copyright 2020 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="gr-a11y-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="shared-styles">
- :host {
- display: block;
- background-color: var(--view-background-color);
- }
- .hidden {
- display: none;
- }
- gr-patch-range-select {
- display: block;
- }
- gr-diff {
- border: none;
- }
- .stickyHeader {
- background-color: var(--view-background-color);
- position: sticky;
- top: 0;
- /* TODO(dhruvsri): This is required only because of 'position:relative' in
- <gr-diff-highlight> (which could maybe be removed??). */
- z-index: 1;
- box-shadow: var(--elevation-level-1);
- /* This is just for giving the box-shadow some space. */
- margin-bottom: 2px;
- }
- header,
- .subHeader {
- align-items: center;
- display: flex;
- justify-content: space-between;
- }
- header {
- padding: var(--spacing-s) var(--spacing-xl);
- border-bottom: 1px solid var(--border-color);
- }
- .changeNumberColon {
- color: transparent;
- }
- .headerSubject {
- margin-right: var(--spacing-m);
- font-weight: var(--font-weight-bold);
- }
- .patchRangeLeft {
- align-items: center;
- display: flex;
- }
- .navLink:not([href]) {
- color: var(--deemphasized-text-color);
- }
- .navLinks {
- align-items: center;
- display: flex;
- white-space: nowrap;
- }
- .navLink {
- padding: 0 var(--spacing-xs);
- }
- .reviewed {
- display: inline-block;
- margin: 0 var(--spacing-xs);
- vertical-align: top;
- position: relative;
- top: 8px;
- }
- .jumpToFileContainer {
- display: inline-block;
- word-break: break-all;
- }
- .mobile {
- display: none;
- }
- gr-button {
- padding: var(--spacing-s) 0;
- text-decoration: none;
- }
- .loading {
- color: var(--deemphasized-text-color);
- font-family: var(--header-font-family);
- font-size: var(--font-size-h1);
- font-weight: var(--font-weight-h1);
- line-height: var(--line-height-h1);
- height: 100%;
- padding: var(--spacing-l);
- text-align: center;
- }
- .subHeader {
- background-color: var(--background-color-secondary);
- flex-wrap: wrap;
- padding: 0 var(--spacing-l);
- }
- .prefsButton {
- text-align: right;
- }
- .editMode .hideOnEdit {
- display: none;
- }
- .blameLoader,
- .fileNum {
- display: none;
- }
- .blameLoader.show,
- .fileNum.show,
- .download,
- .preferences,
- .rightControls {
- align-items: center;
- display: flex;
- }
- .diffModeSelector,
- .editButton {
- align-items: center;
- display: flex;
- }
- .diffModeSelector span,
- .editButton span {
- margin-right: var(--spacing-xs);
- }
- .diffModeSelector.hide,
- .separator.hide {
- display: none;
- }
- .editButtona a {
- text-decoration: none;
- }
- @media screen and (max-width: 50em) {
- header {
- padding: var(--spacing-s) var(--spacing-l);
- }
- .dash {
- display: none;
- }
- .desktop {
- display: none;
- }
- .fileNav {
- align-items: flex-start;
- display: flex;
- margin: 0 var(--spacing-xs);
- }
- .fullFileName {
- display: block;
- font-style: italic;
- min-width: 50%;
- padding: 0 var(--spacing-xxs);
- text-align: center;
- width: 100%;
- word-wrap: break-word;
- }
- .reviewed {
- vertical-align: -1px;
- }
- .mobileNavLink {
- color: var(--primary-text-color);
- font-family: var(--header-font-family);
- font-size: var(--font-size-h2);
- font-weight: var(--font-weight-h2);
- line-height: var(--line-height-h2);
- text-decoration: none;
- }
- .mobileNavLink:not([href]) {
- color: var(--deemphasized-text-color);
- }
- .jumpToFileContainer {
- display: block;
- width: 100%;
- word-break: break-all;
- }
- /* prettier formatter removes semi-colons after css mixins. */
- /* prettier-ignore */
- gr-dropdown-list {
- width: 100%;
- --gr-select-style-width: 100%;
- --gr-select-style-display: block;
- --native-select-style-width: 100%;
- }
- }
- :host(.hideComments) {
- --gr-comment-thread-display: none;
- }
- </style>
- <div class$="stickyHeader [[_computeContainerClass(_editMode)]]">
- <h1 class="assistive-tech-only">
- Diff of [[_computeTruncatedPath(_path)]]
- </h1>
- <header>
- <div>
- <a
- href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]"
- >[[_changeNum]]</a
- ><!--
- --><span class="changeNumberColon">:</span>
- <span class="headerSubject">[[_change.subject]]</span>
- <input
- id="reviewed"
- class="reviewed hideOnEdit"
- type="checkbox"
- on-change="_handleReviewedChange"
- hidden$="[[!_loggedIn]]"
- hidden=""
- title="Toggle reviewed status of file"
- aria-label="file reviewed"
- /><!--
- -->
- <div class="jumpToFileContainer">
- <gr-dropdown-list
- id="dropdown"
- value="[[_path]]"
- on-value-change="_handleFileChange"
- items="[[_formattedFiles]]"
- initial-count="75"
- show-copy-for-trigger-text
- >
- </gr-dropdown-list>
- </div>
- </div>
- <div class="navLinks desktop">
- <span
- class$="fileNum [[_computeFileNumClass(_fileNum, _formattedFiles)]]"
- >
- File [[_fileNum]] of [[_formattedFiles.length]]
- <span class="separator"></span>
- </span>
- <a
- class="navLink"
- title="[[createTitle(Shortcut.PREV_FILE,
- ShortcutSection.NAVIGATION)]]"
- href$="[[_computeNavLinkURL(_change, _path, _fileList, -1)]]"
- >
- Prev</a
- >
- <span class="separator"></span>
- <a
- class="navLink"
- title="[[createTitle(Shortcut.UP_TO_CHANGE,
- ShortcutSection.NAVIGATION)]]"
- href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]"
- >
- Up</a
- >
- <span class="separator"></span>
- <a
- class="navLink"
- title="[[createTitle(Shortcut.NEXT_FILE,
- ShortcutSection.NAVIGATION)]]"
- href$="[[_computeNavLinkURL(_change, _path, _fileList, 1)]]"
- >
- Next</a
- >
- </div>
- </header>
- <div class="subHeader">
- <div class="patchRangeLeft">
- <gr-patch-range-select
- id="rangeSelect"
- change-num="[[_changeNum]]"
- patch-num="[[_patchRange.patchNum]]"
- base-patch-num="[[_patchRange.basePatchNum]]"
- files-weblinks="[[_filesWeblinks]]"
- available-patches="[[_allPatchSets]]"
- revisions="[[_change.revisions]]"
- revision-info="[[_revisionInfo]]"
- on-patch-range-change="_handlePatchChange"
- >
- </gr-patch-range-select>
- <span class="download desktop">
- <span class="separator"></span>
- <gr-dropdown
- link=""
- down-arrow=""
- items="[[_computeDownloadDropdownLinks(_change.project, _changeNum, _patchRange, _path, _diff)]]"
- horizontal-align="left"
- >
- <span class="downloadTitle"> Download </span>
- </gr-dropdown>
- </span>
- </div>
- <div class="rightControls">
- <span
- class$="blameLoader [[_computeBlameLoaderClass(_isImageDiff, _path)]]"
- >
- <gr-button
- link=""
- id="toggleBlame"
- title="[[createTitle(Shortcut.TOGGLE_BLAME, ShortcutSection.DIFFS)]]"
- disabled="[[_isBlameLoading]]"
- on-click="_toggleBlame"
- >[[_computeBlameToggleLabel(_isBlameLoaded,
- _isBlameLoading)]]</gr-button
- >
- </span>
- <template
- is="dom-if"
- if="[[_computeCanEdit(_loggedIn, _editWeblinks, _change.*)]]"
- >
- <span class="separator"></span>
- <span class="editButton">
- <gr-button
- link=""
- title="Edit current file"
- on-click="_goToEditFile"
- >edit</gr-button
- >
- </span>
- </template>
- <template is="dom-if" if="[[_computeShowEditLinks(_editWeblinks)]]">
- <span class="separator"></span>
- <template is="dom-repeat" items="[[_editWeblinks]]" as="weblink">
- <a target="_blank" href$="[[weblink.url]]">[[weblink.name]]</a>
- </template>
- </template>
- <span class="separator"></span>
- <div class$="diffModeSelector [[_computeModeSelectHideClass(_diff)]]">
- <span>Diff view:</span>
- <gr-diff-mode-selector
- id="modeSelect"
- save-on-change="[[_loggedIn]]"
- show-tooltip-below=""
- ></gr-diff-mode-selector>
- </div>
- <span
- id="diffPrefsContainer"
- hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
- hidden=""
- >
- <span class="preferences desktop">
- <gr-tooltip-content
- has-tooltip=""
- position-below=""
- title="Diff preferences"
- >
- <gr-button link="" class="prefsButton" on-click="_handlePrefsTap"
- ><iron-icon icon="gr-icons:settings"></iron-icon
- ></gr-button>
- </gr-tooltip-content>
- </span>
- </span>
- <gr-endpoint-decorator name="annotation-toggler">
- <span hidden="" id="annotation-span">
- <label for="annotation-checkbox" id="annotation-label"></label>
- <iron-input type="checkbox" disabled="">
- <input
- is="iron-input"
- type="checkbox"
- id="annotation-checkbox"
- disabled=""
- />
- </iron-input>
- </span>
- </gr-endpoint-decorator>
- </div>
- </div>
- <div class="fileNav mobile">
- <a
- class="mobileNavLink"
- href$="[[_computeNavLinkURL(_change, _path, _fileList, -1)]]"
- >
- <</a
- >
- <div class="fullFileName mobile">[[_computeDisplayPath(_path)]]</div>
- <a
- class="mobileNavLink"
- href$="[[_computeNavLinkURL(_change, _path, _fileList, 1)]]"
- >
- ></a
- >
- </div>
- </div>
- <div class="loading" hidden$="[[!_loading]]">Loading...</div>
- <h2 class="assistive-tech-only">Diff view</h2>
- <gr-diff-host
- id="diffHost"
- hidden=""
- hidden$="[[_loading]]"
- change-num="[[_changeNum]]"
- change="[[_change]]"
- commit-range="[[_commitRange]]"
- patch-range="[[_patchRange]]"
- file="[[_file]]"
- path="[[_path]]"
- project-name="[[_change.project]]"
- on-is-blame-loaded-changed="_onIsBlameLoadedChanged"
- on-comment-anchor-tap="_onLineSelected"
- on-line-selected="_onLineSelected"
- on-diff-changed="_onDiffChanged"
- on-edit-weblinks-changed="_onEditWeblinksChanged"
- on-files-weblinks-changed="_onFilesWeblinksChanged"
- on-is-image-diff-changed="_onIsImageDiffChanged"
- >
- </gr-diff-host>
- <gr-apply-fix-dialog
- id="applyFixDialog"
- change="[[_change]]"
- change-num="[[_changeNum]]"
- >
- </gr-apply-fix-dialog>
- <gr-diff-preferences-dialog
- id="diffPreferencesDialog"
- on-reload-diff-preference="_handleReloadingDiffPreference"
- >
- </gr-diff-preferences-dialog>
- <gr-overlay id="downloadOverlay">
- <gr-download-dialog
- id="downloadDialog"
- change="[[_change]]"
- patch-num="[[_patchRange.patchNum]]"
- config="[[_serverConfig.download]]"
- on-close="_handleDownloadDialogClose"
- ></gr-download-dialog>
- </gr-overlay>
-`;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
index b375219..799b11a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
@@ -64,6 +64,7 @@
import {CommentMap} from '../../../utils/comment-util';
import {ParsedChangeInfo} from '../../../types/types';
import {assertIsDefined} from '../../../utils/common-util';
+import {GrDiffModeSelector} from '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
const basicFixture = fixtureFromElement('gr-diff-view');
@@ -119,22 +120,21 @@
stubRestApi('getPortedComments').returns(Promise.resolve({}));
element = basicFixture.instantiate();
- element._changeNum = 42 as NumericChangeId;
- element._path = 'some/path.txt';
- element._change = createParsedChange();
- element._diff = {...createDiff(), content: []};
+ element.changeNum = 42 as NumericChangeId;
+ element.path = 'some/path.txt';
+ element.change = createParsedChange();
+ element.diff = {...createDiff(), content: []};
getDiffRestApiStub = stubRestApi('getDiff');
- // Delayed in case a test updates element._diff.
- getDiffRestApiStub.callsFake(() => Promise.resolve(element._diff));
- element._patchRange = createPatchRange();
- element._changeComments = new ChangeComments({
+ // Delayed in case a test updates element.diff.
+ getDiffRestApiStub.callsFake(() => Promise.resolve(element.diff));
+ element.patchRange = createPatchRange();
+ element.changeComments = new ChangeComments({
'/COMMIT_MSG': [
createComment('c1', 10, 2, '/COMMIT_MSG'),
createComment('c3', 10, PARENT, '/COMMIT_MSG'),
],
});
- await flush();
- await element.$.diffHost.updateComplete;
+ await element.updateComplete;
element.getCommentsModel().setState({
comments: {},
@@ -153,10 +153,11 @@
test('params change triggers diffViewDisplayed()', () => {
const diffViewDisplayedStub = stubReporting('diffViewDisplayed');
- sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
sinon.stub(element, 'initPatchRange');
- sinon.stub(element, '_getFiles');
- const paramsChangedSpy = sinon.spy(element, '_paramsChanged');
+ sinon.stub(element, 'fetchFiles');
+ const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
element.params = {
view: GerritNav.View.DIFF,
changeNum: 42 as NumericChangeId,
@@ -164,8 +165,8 @@
basePatchNum: 1 as BasePatchSetNum,
path: '/COMMIT_MSG',
};
- element._path = '/COMMIT_MSG';
- element._patchRange = createPatchRange();
+ element.path = '/COMMIT_MSG';
+ element.patchRange = createPatchRange();
return paramsChangedSpy.returnValues[0]?.then(() => {
assert.isTrue(diffViewDisplayedStub.calledOnce);
});
@@ -183,10 +184,11 @@
);
getUrlStub = sinon.stub(GerritNav, 'getUrlForDiffById');
replaceStateStub = sinon.stub(history, 'replaceState');
- sinon.stub(element, '_getFiles');
+ sinon.stub(element, 'fetchFiles');
stubReporting('diffViewDisplayed');
- sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
- paramsChangedSpy = sinon.spy(element, '_paramsChanged');
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
+ paramsChangedSpy = sinon.spy(element, 'paramsChanged');
element.getChangeModel().setState({
change: {
...createParsedChange(),
@@ -218,7 +220,7 @@
path: 'abcd',
patchNum: 1 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
revisions: createRevisions(11),
};
@@ -226,12 +228,9 @@
assert.isTrue(
initLineOfInterestAndCursorStub.calledWithExactly(true)
);
- assert.equal(element._focusLineNum, 10);
- assert.equal(
- element._patchRange?.patchNum,
- 11 as RevisionPatchSetNum
- );
- assert.equal(element._patchRange?.basePatchNum, 2 as BasePatchSetNum);
+ assert.equal(element.focusLineNum, 10);
+ assert.equal(element.patchRange?.patchNum, 11 as RevisionPatchSetNum);
+ assert.equal(element.patchRange?.basePatchNum, 2 as BasePatchSetNum);
assert.isTrue(replaceStateStub.called);
assert.isTrue(
getUrlStub.calledWithExactly(
@@ -250,13 +249,14 @@
test('params change causes blame to load if it was set to true', () => {
// Blame loads for subsequent files if it was loaded for one file
- element._isBlameLoaded = true;
+ element.isBlameLoaded = true;
stubReporting('diffViewDisplayed');
const loadBlameStub = sinon.stub(element, 'loadBlame');
- sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
- const paramsChangedSpy = sinon.spy(element, '_paramsChanged');
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
+ const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
sinon.stub(element, 'initPatchRange');
- sinon.stub(element, '_getFiles');
+ sinon.stub(element, 'fetchFiles');
element.params = {
view: GerritNav.View.DIFF,
changeNum: 42 as NumericChangeId,
@@ -264,10 +264,10 @@
basePatchNum: 1 as BasePatchSetNum,
path: '/COMMIT_MSG',
};
- element._path = '/COMMIT_MSG';
- element._patchRange = createPatchRange();
+ element.path = '/COMMIT_MSG';
+ element.patchRange = createPatchRange();
return paramsChangedSpy.returnValues[0]!.then(() => {
- assert.isTrue(element._isBlameLoaded);
+ assert.isTrue(element.isBlameLoaded);
assert.isTrue(loadBlameStub.calledOnce);
});
});
@@ -289,9 +289,10 @@
});
stubReporting('diffViewDisplayed');
sinon.stub(element, 'loadBlame');
- sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
sinon.stub(element, 'isFileUnchanged').returns(true);
- const paramsChangedSpy = sinon.spy(element, '_paramsChanged');
+ const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
element.getChangeModel().setState({
change: {
...createParsedChange(),
@@ -306,14 +307,14 @@
commentLink: true,
commentId: 'c1' as UrlEncodedCommentId,
};
- element._change = {
+ element.change = {
...createParsedChange(),
revisions: createRevisions(11),
};
return paramsChangedSpy.returnValues[0]?.then(() => {
assert.isTrue(
diffNavStub.lastCall.calledWithExactly(
- element._change!,
+ element.change!,
'/COMMIT_MSG',
2 as RevisionPatchSetNum,
PARENT,
@@ -340,9 +341,10 @@
});
stubReporting('diffViewDisplayed');
sinon.stub(element, 'loadBlame');
- sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
sinon.stub(element, 'isFileUnchanged').returns(true);
- const paramsChangedSpy = sinon.spy(element, '_paramsChanged');
+ const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
element.getChangeModel().setState({
change: {
...createParsedChange(),
@@ -357,7 +359,7 @@
commentLink: true,
commentId: 'c3' as UrlEncodedCommentId,
};
- element._change = {
+ element.change = {
...createParsedChange(),
revisions: createRevisions(11),
};
@@ -415,9 +417,10 @@
stubReporting('diffViewDisplayed');
sinon.stub(element, 'loadBlame');
- sinon.stub(element.$.diffHost, 'reload').returns(Promise.resolve());
- const paramsChangedSpy = sinon.spy(element, '_paramsChanged');
- element._change = undefined;
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
+ const paramsChangedSpy = sinon.spy(element, 'paramsChanged');
+ element.change = undefined;
element.getChangeModel().setState({
change: {
...createParsedChange(),
@@ -425,7 +428,7 @@
},
loadingStatus: LoadingStatus.LOADED,
});
- element._patchRange = {
+ element.patchRange = {
patchNum: 2 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
@@ -443,139 +446,138 @@
});
test('toggle left diff with a hotkey', () => {
- const toggleLeftDiffStub = sinon.stub(
- element.$.diffHost,
- 'toggleLeftDiff'
- );
+ assertIsDefined(element.diffHost);
+ const toggleLeftDiffStub = sinon.stub(element.diffHost, 'toggleLeftDiff');
MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'A');
assert.isTrue(toggleLeftDiffStub.calledOnce);
});
test('keyboard shortcuts', async () => {
clock = sinon.useFakeTimers();
- element._changeNum = 42 as NumericChangeId;
+ element.changeNum = 42 as NumericChangeId;
element.getBrowserModel().setScreenWidth(0);
- element._patchRange = {
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 10 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
a: createRevision(10),
},
};
- element._files = getFilesFromFileList([
+ element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
'wheatley.md',
]);
- element._path = 'glados.txt';
- element._loggedIn = true;
- await flush();
+ element.path = 'glados.txt';
+ element.loggedIn = true;
+ await element.updateComplete;
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(
- changeNavStub.lastCall.calledWith(element._change),
+ changeNavStub.lastCall.calledWith(element.change),
'Should navigate to /c/42/'
);
- await flush();
+ await element.updateComplete;
MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(
diffNavStub.lastCall.calledWith(
- element._change,
+ element.change,
'wheatley.md',
10 as RevisionPatchSetNum,
PARENT
),
'Should navigate to /c/42/10/wheatley.md'
);
- element._path = 'wheatley.md';
- await flush();
+ element.path = 'wheatley.md';
+ await element.updateComplete;
- assert.isTrue(element._loading);
+ assert.isTrue(element.loading);
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(
diffNavStub.lastCall.calledWith(
- element._change,
+ element.change,
'glados.txt',
10 as RevisionPatchSetNum,
PARENT
),
'Should navigate to /c/42/10/glados.txt'
);
- element._path = 'glados.txt';
- await flush();
+ element.path = 'glados.txt';
+ await element.updateComplete;
- assert.isTrue(element._loading);
+ assert.isTrue(element.loading);
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(
diffNavStub.lastCall.calledWith(
- element._change,
+ element.change,
'chell.go',
10 as RevisionPatchSetNum,
PARENT
),
'Should navigate to /c/42/10/chell.go'
);
- element._path = 'chell.go';
- await flush();
+ element.path = 'chell.go';
+ await element.updateComplete;
- assert.isTrue(element._loading);
+ assert.isTrue(element.loading);
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(
- changeNavStub.lastCall.calledWith(element._change),
+ changeNavStub.lastCall.calledWith(element.change),
'Should navigate to /c/42/'
);
- await flush();
- assert.isTrue(element._loading);
+ await element.updateComplete;
+ assert.isTrue(element.loading);
+ assertIsDefined(element.diffPreferencesDialog);
const showPrefsStub = sinon
- .stub(element.$.diffPreferencesDialog, 'open')
+ .stub(element.diffPreferencesDialog, 'open')
.callsFake(() => Promise.resolve());
MockInteractions.pressAndReleaseKeyOn(element, 188, null, ',');
- await flush();
+ await element.updateComplete;
assert(showPrefsStub.calledOnce);
assertIsDefined(element.cursor);
let scrollStub = sinon.stub(element.cursor, 'moveToNextChunk');
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
- await flush();
+ await element.updateComplete;
assert(scrollStub.calledOnce);
scrollStub = sinon.stub(element.cursor, 'moveToPreviousChunk');
MockInteractions.pressAndReleaseKeyOn(element, 80, null, 'p');
- await flush();
+ await element.updateComplete;
assert(scrollStub.calledOnce);
scrollStub = sinon.stub(element.cursor, 'moveToNextCommentThread');
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'N');
- await flush();
+ await element.updateComplete;
assert(scrollStub.calledOnce);
scrollStub = sinon.stub(element.cursor, 'moveToPreviousCommentThread');
MockInteractions.pressAndReleaseKeyOn(element, 80, null, 'P');
- await flush();
+ await element.updateComplete;
assert(scrollStub.calledOnce);
- assertIsDefined(element.$.diffHost.diffElement);
+ assertIsDefined(element.diffHost);
+ assertIsDefined(element.diffHost.diffElement);
const computeContainerClassStub = sinon.stub(
- element.$.diffHost.diffElement,
+ element.diffHost.diffElement,
'_computeContainerClass'
);
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
- await flush();
- await element.$.diffHost.updateComplete;
- await flush();
+
+ await element.updateComplete;
assert(
computeContainerClassStub.lastCall.calledWithExactly(
true,
@@ -585,7 +587,7 @@
);
MockInteractions.pressAndReleaseKeyOn(element, 27, null, 'Escape');
- await flush();
+ await element.updateComplete;
assert(
computeContainerClassStub.lastCall.calledWithExactly(
true,
@@ -595,10 +597,11 @@
);
// Note that stubbing setReviewed means that the value of the
- // `element.$.reviewed` checkbox is not flipped.
+ // `element.reviewed` checkbox is not flipped.
const setReviewedStub = sinon.stub(element, 'setReviewed');
const handleToggleSpy = sinon.spy(element, 'handleToggleFileReviewed');
- element.$.reviewed.checked = false;
+ assertIsDefined(element.reviewed);
+ element.reviewed.checked = false;
assert.isFalse(handleToggleSpy.called);
assert.isFalse(setReviewedStub.called);
@@ -620,40 +623,40 @@
clock.restore();
});
- test('moveToNextCommentThread navigates to next file', () => {
+ test('moveToNextCommentThread navigates to next file', async () => {
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
- const diffChangeStub = sinon.stub(element, '_navigateToChange');
+ const diffChangeStub = sinon.stub(element, 'navigateToChange');
assertIsDefined(element.cursor);
sinon.stub(element.cursor, 'isAtEnd').returns(true);
- element._changeNum = 42 as NumericChangeId;
+ element.changeNum = 42 as NumericChangeId;
const comment: PathToCommentsInfoMap = {
'wheatley.md': [createComment('c2', 21, 10, 'wheatley.md')],
};
- element._changeComments = new ChangeComments(comment);
- element._patchRange = {
+ element.changeComments = new ChangeComments(comment);
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 10 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
a: createRevision(10),
},
};
- element._files = getFilesFromFileList([
+ element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
'wheatley.md',
]);
- element._path = 'glados.txt';
- element._loggedIn = true;
+ element.path = 'glados.txt';
+ element.loggedIn = true;
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'N');
- flush();
+ await element.updateComplete;
assert.isTrue(
diffNavStub.calledWithExactly(
- element._change,
+ element.change,
'wheatley.md',
10 as RevisionPatchSetNum,
PARENT,
@@ -661,26 +664,28 @@
)
);
- element._path = 'wheatley.md'; // navigated to next file
+ element.path = 'wheatley.md'; // navigated to next file
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'N');
- flush();
+ await element.updateComplete;
assert.isTrue(diffChangeStub.called);
});
- test('shift+x shortcut toggles all diff context', () => {
- const toggleStub = sinon.stub(element.$.diffHost, 'toggleAllContext');
+ test('shift+x shortcut toggles all diff context', async () => {
+ assertIsDefined(element.diffHost);
+ const toggleStub = sinon.stub(element.diffHost, 'toggleAllContext');
MockInteractions.pressAndReleaseKeyOn(element, 88, null, 'X');
- flush();
+ await element.updateComplete;
assert.isTrue(toggleStub.called);
});
- test('diff against base', () => {
- element._patchRange = {
+ test('diff against base', async () => {
+ element.patchRange = {
basePatchNum: 5 as BasePatchSetNum,
patchNum: 10 as RevisionPatchSetNum,
};
+ await element.updateComplete;
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
element.handleDiffAgainstBase();
const args = diffNavStub.getCall(0).args;
@@ -688,15 +693,17 @@
assert.isNotOk(args[3]);
});
- test('diff against latest', () => {
- element._change = {
+ test('diff against latest', async () => {
+ element.path = 'foo';
+ element.change = {
...createParsedChange(),
revisions: createRevisions(12),
};
- element._patchRange = {
+ element.patchRange = {
basePatchNum: 5 as BasePatchSetNum,
patchNum: 10 as RevisionPatchSetNum,
};
+ await element.updateComplete;
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
element.handleDiffAgainstLatest();
const args = diffNavStub.getCall(0).args;
@@ -704,12 +711,13 @@
assert.equal(args[3], 5 as BasePatchSetNum);
});
- test('handleDiffBaseAgainstLeft', () => {
- element._change = {
+ test('handleDiffBaseAgainstLeft', async () => {
+ element.path = 'foo';
+ element.change = {
...createParsedChange(),
revisions: createRevisions(10),
};
- element._patchRange = {
+ element.patchRange = {
patchNum: 3 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
@@ -717,6 +725,7 @@
view: GerritView.DASHBOARD,
dashboard: 'id' as DashboardId,
};
+ await element.updateComplete;
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
element.handleDiffBaseAgainstLeft();
assert(diffNavStub.called);
@@ -727,21 +736,21 @@
});
test('handleDiffBaseAgainstLeft when initially navigating to a comment', () => {
- element._change = {
+ element.change = {
...createParsedChange(),
revisions: createRevisions(10),
};
- element._patchRange = {
+ element.patchRange = {
patchNum: 3 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
- sinon.stub(element, '_paramsChanged');
+ sinon.stub(element, 'paramsChanged');
element.params = {
commentLink: true,
view: GerritView.DIFF,
changeNum: 42 as NumericChangeId,
};
- element._focusLineNum = 10;
+ element.focusLineNum = 10;
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
element.handleDiffBaseAgainstLeft();
assert(diffNavStub.called);
@@ -751,15 +760,17 @@
assert.equal(args[4], 10);
});
- test('handleDiffRightAgainstLatest', () => {
- element._change = {
+ test('handleDiffRightAgainstLatest', async () => {
+ element.path = 'foo';
+ element.change = {
...createParsedChange(),
revisions: createRevisions(10),
};
- element._patchRange = {
+ element.patchRange = {
basePatchNum: 1 as BasePatchSetNum,
patchNum: 3 as RevisionPatchSetNum,
};
+ await element.updateComplete;
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
element.handleDiffRightAgainstLatest();
assert(diffNavStub.called);
@@ -768,15 +779,16 @@
assert.equal(args[3], 3 as BasePatchSetNum);
});
- test('handleDiffBaseAgainstLatest', () => {
- element._change = {
+ test('handleDiffBaseAgainstLatest', async () => {
+ element.change = {
...createParsedChange(),
revisions: createRevisions(10),
};
- element._patchRange = {
+ element.patchRange = {
basePatchNum: 1 as BasePatchSetNum,
patchNum: 3 as RevisionPatchSetNum,
};
+ await element.updateComplete;
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
element.handleDiffBaseAgainstLatest();
assert(diffNavStub.called);
@@ -787,11 +799,11 @@
test('A fires an error event when not logged in', async () => {
const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
- sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(false));
+ element.loggedIn = false;
const loggedInErrorSpy = sinon.spy();
element.addEventListener('show-auth-required', loggedInErrorSpy);
MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
- await flush();
+ await element.updateComplete;
assert.isTrue(
changeNavStub.notCalled,
'The `a` keyboard shortcut ' +
@@ -801,12 +813,12 @@
});
test('A navigates to change with logged in', async () => {
- element._changeNum = 42 as NumericChangeId;
- element._patchRange = {
+ element.changeNum = 42 as NumericChangeId;
+ element.patchRange = {
basePatchNum: 5 as BasePatchSetNum,
patchNum: 10 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
@@ -815,13 +827,13 @@
},
};
const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
- sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+ element.loggedIn = true;
const loggedInErrorSpy = sinon.spy();
element.addEventListener('show-auth-required', loggedInErrorSpy);
MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
- await flush();
+ await element.updateComplete;
assert(
- changeNavStub.lastCall.calledWithExactly(element._change, {
+ changeNavStub.lastCall.calledWithExactly(element.change, {
patchNum: 10 as RevisionPatchSetNum,
basePatchNum: 5 as BasePatchSetNum,
openReplyDialog: true,
@@ -832,12 +844,12 @@
});
test('A navigates to change with old patch number with logged in', async () => {
- element._changeNum = 42 as NumericChangeId;
- element._patchRange = {
+ element.changeNum = 42 as NumericChangeId;
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 1 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
@@ -846,13 +858,13 @@
},
};
const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
- sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+ element.loggedIn = true;
const loggedInErrorSpy = sinon.spy();
element.addEventListener('show-auth-required', loggedInErrorSpy);
MockInteractions.pressAndReleaseKeyOn(element, 65, null, 'a');
- await flush();
+ await element.updateComplete;
assert(
- changeNavStub.lastCall.calledWithExactly(element._change, {
+ changeNavStub.lastCall.calledWithExactly(element.change, {
patchNum: 1 as RevisionPatchSetNum,
basePatchNum: PARENT,
openReplyDialog: true,
@@ -863,12 +875,12 @@
});
test('keyboard shortcuts with patch range', () => {
- element._changeNum = 42 as NumericChangeId;
- element._patchRange = {
+ element.changeNum = 42 as NumericChangeId;
+ element.patchRange = {
basePatchNum: 5 as BasePatchSetNum,
patchNum: 10 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
@@ -876,19 +888,19 @@
b: createRevision(5),
},
};
- element._files = getFilesFromFileList([
+ element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
'wheatley.md',
]);
- element._path = 'glados.txt';
+ element.path = 'glados.txt';
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(
- changeNavStub.lastCall.calledWithExactly(element._change, {
+ changeNavStub.lastCall.calledWithExactly(element.change, {
patchNum: 10 as RevisionPatchSetNum,
basePatchNum: 5 as BasePatchSetNum,
openReplyDialog: false,
@@ -897,10 +909,10 @@
);
MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
- assert.isTrue(element._loading);
+ assert.isTrue(element.loading);
assert(
diffNavStub.lastCall.calledWithExactly(
- element._change,
+ element.change,
'wheatley.md',
10 as RevisionPatchSetNum,
5 as BasePatchSetNum,
@@ -908,13 +920,13 @@
),
'Should navigate to /c/42/5..10/wheatley.md'
);
- element._path = 'wheatley.md';
+ element.path = 'wheatley.md';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
- assert.isTrue(element._loading);
+ assert.isTrue(element.loading);
assert(
diffNavStub.lastCall.calledWithExactly(
- element._change,
+ element.change,
'glados.txt',
10 as RevisionPatchSetNum,
5 as BasePatchSetNum,
@@ -922,13 +934,13 @@
),
'Should navigate to /c/42/5..10/glados.txt'
);
- element._path = 'glados.txt';
+ element.path = 'glados.txt';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
- assert.isTrue(element._loading);
+ assert.isTrue(element.loading);
assert(
diffNavStub.lastCall.calledWithExactly(
- element._change,
+ element.change,
'chell.go',
10 as RevisionPatchSetNum,
5 as BasePatchSetNum,
@@ -936,12 +948,12 @@
),
'Should navigate to /c/42/5..10/chell.go'
);
- element._path = 'chell.go';
+ element.path = 'chell.go';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
- assert.isTrue(element._loading);
+ assert.isTrue(element.loading);
assert(
- changeNavStub.lastCall.calledWithExactly(element._change, {
+ changeNavStub.lastCall.calledWithExactly(element.change, {
patchNum: 10 as RevisionPatchSetNum,
basePatchNum: 5 as BasePatchSetNum,
openReplyDialog: false,
@@ -949,20 +961,21 @@
'Should navigate to /c/42/5..10'
);
+ assertIsDefined(element.downloadOverlay);
const downloadOverlayStub = sinon
- .stub(element.$.downloadOverlay, 'open')
+ .stub(element.downloadOverlay, 'open')
.returns(Promise.resolve());
MockInteractions.pressAndReleaseKeyOn(element, 68, null, 'd');
assert.isTrue(downloadOverlayStub.called);
});
test('keyboard shortcuts with old patch number', () => {
- element._changeNum = 42 as NumericChangeId;
- element._patchRange = {
+ element.changeNum = 42 as NumericChangeId;
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 1 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
@@ -970,19 +983,19 @@
b: createRevision(2),
},
};
- element._files = getFilesFromFileList([
+ element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
'wheatley.md',
]);
- element._path = 'glados.txt';
+ element.path = 'glados.txt';
const diffNavStub = sinon.stub(GerritNav, 'navigateToDiff');
const changeNavStub = sinon.stub(GerritNav, 'navigateToChange');
MockInteractions.pressAndReleaseKeyOn(element, 85, null, 'u');
assert(
- changeNavStub.lastCall.calledWithExactly(element._change, {
+ changeNavStub.lastCall.calledWithExactly(element.change, {
patchNum: 1 as RevisionPatchSetNum,
basePatchNum: PARENT,
openReplyDialog: false,
@@ -993,7 +1006,7 @@
MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(
diffNavStub.lastCall.calledWithExactly(
- element._change,
+ element.change,
'wheatley.md',
1 as RevisionPatchSetNum,
PARENT,
@@ -1001,12 +1014,12 @@
),
'Should navigate to /c/42/1/wheatley.md'
);
- element._path = 'wheatley.md';
+ element.path = 'wheatley.md';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(
diffNavStub.lastCall.calledWithExactly(
- element._change,
+ element.change,
'glados.txt',
1 as RevisionPatchSetNum,
PARENT,
@@ -1014,12 +1027,12 @@
),
'Should navigate to /c/42/1/glados.txt'
);
- element._path = 'glados.txt';
+ element.path = 'glados.txt';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(
diffNavStub.lastCall.calledWithExactly(
- element._change,
+ element.change,
'chell.go',
1 as RevisionPatchSetNum,
PARENT,
@@ -1027,12 +1040,12 @@
),
'Should navigate to /c/42/1/chell.go'
);
- element._path = 'chell.go';
+ element.path = 'chell.go';
changeNavStub.reset();
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(
- changeNavStub.lastCall.calledWithExactly(element._change, {
+ changeNavStub.lastCall.calledWithExactly(element.change, {
patchNum: 1 as RevisionPatchSetNum,
basePatchNum: PARENT,
openReplyDialog: false,
@@ -1043,13 +1056,13 @@
});
test('edit should redirect to edit page', async () => {
- element._loggedIn = true;
- element._path = 't.txt';
- element._patchRange = {
+ element.loggedIn = true;
+ element.path = 't.txt';
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 1 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
project: 'gerrit' as RepoName,
@@ -1060,7 +1073,7 @@
},
};
const redirectStub = sinon.stub(GerritNav, 'navigateToRelativeUrl');
- await flush();
+ await element.updateComplete;
const editBtn = queryAndAssert(element, '.editButton gr-button');
assert.isTrue(!!editBtn);
MockInteractions.tap(editBtn);
@@ -1068,9 +1081,9 @@
assert.isTrue(
redirectStub.lastCall.calledWithExactly(
GerritNav.getEditUrlForDiff(
- element._change,
- element._path,
- element._patchRange.patchNum
+ element.change,
+ element.path,
+ element.patchRange.patchNum
)
)
);
@@ -1078,13 +1091,13 @@
test('edit should redirect to edit page with line number', async () => {
const lineNumber = 42;
- element._loggedIn = true;
- element._path = 't.txt';
- element._patchRange = {
+ element.loggedIn = true;
+ element.path = 't.txt';
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 1 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
project: 'gerrit' as RepoName,
@@ -1099,7 +1112,7 @@
.stub(element.cursor, 'getAddress')
.returns({number: lineNumber, leftSide: false});
const redirectStub = sinon.stub(GerritNav, 'navigateToRelativeUrl');
- await flush();
+ await element.updateComplete;
const editBtn = queryAndAssert(element, '.editButton gr-button');
assert.isTrue(!!editBtn);
MockInteractions.tap(editBtn);
@@ -1107,9 +1120,9 @@
assert.isTrue(
redirectStub.lastCall.calledWithExactly(
GerritNav.getEditUrlForDiff(
- element._change,
- element._path,
- element._patchRange.patchNum,
+ element.change,
+ element.path,
+ element.patchRange.patchNum,
lineNumber
)
)
@@ -1124,13 +1137,13 @@
changeStatus: ChangeStatus;
}) {
return new Promise(resolve => {
- element._loggedIn = loggedIn;
- element._path = 't.txt';
- element._patchRange = {
+ element.loggedIn = loggedIn;
+ element.path = 't.txt';
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 1 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
status: changeStatus,
@@ -1192,34 +1205,34 @@
});
suite('diff prefs hidden', () => {
- test('when no prefs or logged out', () => {
- element._prefs = undefined;
- element._loggedIn = false;
- flush();
- assert.isTrue(element.$.diffPrefsContainer.hidden);
+ test('when no prefs or logged out', async () => {
+ const getDiffPrefsContainer = () =>
+ query<HTMLSpanElement>(element, '#diffPrefsContainer');
+ element.prefs = undefined;
+ element.loggedIn = false;
+ await element.updateComplete;
+ assert.isNotOk(getDiffPrefsContainer());
- element._loggedIn = true;
- flush();
- assert.isTrue(element.$.diffPrefsContainer.hidden);
+ element.loggedIn = true;
+ await element.updateComplete;
+ assert.isNotOk(getDiffPrefsContainer());
- element._loggedIn = false;
- element._prefs = {...createDefaultDiffPrefs(), font_size: 12};
- flush();
- assert.isTrue(element.$.diffPrefsContainer.hidden);
+ element.loggedIn = false;
+ element.prefs = {...createDefaultDiffPrefs(), font_size: 12};
+ await element.updateComplete;
+ assert.isNotOk(getDiffPrefsContainer());
- element._loggedIn = true;
- element._prefs = {...createDefaultDiffPrefs(), font_size: 12};
- flush();
- assert.isFalse(element.$.diffPrefsContainer.hidden);
+ element.loggedIn = true;
+ element.prefs = {...createDefaultDiffPrefs(), font_size: 12};
+ await element.updateComplete;
+ assert.isOk(getDiffPrefsContainer());
});
});
test('prefsButton opens gr-diff-preferences', () => {
- const handlePrefsTapSpy = sinon.spy(element, '_handlePrefsTap');
- const overlayOpenStub = sinon.stub(
- element.$.diffPreferencesDialog,
- 'open'
- );
+ const handlePrefsTapSpy = sinon.spy(element, 'handlePrefsTap');
+ assertIsDefined(element.diffPreferencesDialog);
+ const overlayOpenStub = sinon.stub(element.diffPreferencesDialog, 'open');
const prefsButton = queryAndAssert(element, '.prefsButton');
MockInteractions.tap(prefsButton);
@@ -1229,7 +1242,7 @@
suite('url params', () => {
setup(() => {
- sinon.stub(element, '_getFiles');
+ sinon.stub(element, 'fetchFiles');
sinon
.stub(GerritNav, 'getUrlForDiff')
.callsFake((c, p, pn, bpn) => `${c._number}-${p}-${pn}-${bpn}`);
@@ -1241,23 +1254,23 @@
});
test('_formattedFiles', () => {
- element._changeNum = 42 as NumericChangeId;
- element._patchRange = {
+ element.changeNum = 42 as NumericChangeId;
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 10 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
};
- element._files = getFilesFromFileList([
+ element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
'wheatley.md',
'/COMMIT_MSG',
'/MERGE_LIST',
]);
- element._path = 'glados.txt';
+ element.path = 'glados.txt';
const expectedFormattedFiles: DropdownItem[] = [
{
text: 'chell.go',
@@ -1311,30 +1324,33 @@
},
];
- assert.deepEqual(element._formattedFiles, expectedFormattedFiles);
- assert.equal(element._formattedFiles?.[1].value, element._path);
+ const result = element.formatFilesForDropdown();
+
+ assert.deepEqual(result, expectedFormattedFiles);
+ assert.equal(result[1].value, element.path);
});
- test('prev/up/next links', () => {
- element._changeNum = 42 as NumericChangeId;
- element._patchRange = {
+ test('prev/up/next links', async () => {
+ element.changeNum = 42 as NumericChangeId;
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 10 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
a: createRevision(10),
},
};
- element._files = getFilesFromFileList([
+ element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
'wheatley.md',
]);
- element._path = 'glados.txt';
- flush();
+ element.path = 'glados.txt';
+ await element.updateComplete;
+
const linkEls = queryAll(element, '.navLink');
assert.equal(linkEls.length, 3);
assert.equal(linkEls[0].getAttribute('href'), '42-chell.go-10-PARENT');
@@ -1343,24 +1359,24 @@
linkEls[2].getAttribute('href'),
'42-wheatley.md-10-PARENT'
);
- element._path = 'wheatley.md';
- flush();
+ element.path = 'wheatley.md';
+ await element.updateComplete;
assert.equal(
linkEls[0].getAttribute('href'),
'42-glados.txt-10-PARENT'
);
assert.equal(linkEls[1].getAttribute('href'), '42-undefined-undefined');
assert.equal(linkEls[2].getAttribute('href'), '42-undefined-undefined');
- element._path = 'chell.go';
- flush();
+ element.path = 'chell.go';
+ await element.updateComplete;
assert.equal(linkEls[0].getAttribute('href'), '42-undefined-undefined');
assert.equal(linkEls[1].getAttribute('href'), '42-undefined-undefined');
assert.equal(
linkEls[2].getAttribute('href'),
'42-glados.txt-10-PARENT'
);
- element._path = 'not_a_real_file';
- flush();
+ element.path = 'not_a_real_file';
+ await element.updateComplete;
assert.equal(
linkEls[0].getAttribute('href'),
'42-wheatley.md-10-PARENT'
@@ -1369,13 +1385,13 @@
assert.equal(linkEls[2].getAttribute('href'), '42-chell.go-10-PARENT');
});
- test('prev/up/next links with patch range', () => {
- element._changeNum = 42 as NumericChangeId;
- element._patchRange = {
+ test('prev/up/next links with patch range', async () => {
+ element.changeNum = 42 as NumericChangeId;
+ element.patchRange = {
basePatchNum: 5 as BasePatchSetNum,
patchNum: 10 as RevisionPatchSetNum,
};
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
@@ -1383,58 +1399,59 @@
b: createRevision(10),
},
};
- element._files = getFilesFromFileList([
+ element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
'wheatley.md',
]);
- element._path = 'glados.txt';
- flush();
+ element.path = 'glados.txt';
+ await element.updateComplete;
const linkEls = queryAll(element, '.navLink');
assert.equal(linkEls.length, 3);
assert.equal(linkEls[0].getAttribute('href'), '42-chell.go-10-5');
assert.equal(linkEls[1].getAttribute('href'), '42-10-5');
assert.equal(linkEls[2].getAttribute('href'), '42-wheatley.md-10-5');
- element._path = 'wheatley.md';
- flush();
+ element.path = 'wheatley.md';
+ await element.updateComplete;
assert.equal(linkEls[0].getAttribute('href'), '42-glados.txt-10-5');
assert.equal(linkEls[1].getAttribute('href'), '42-10-5');
assert.equal(linkEls[2].getAttribute('href'), '42-10-5');
- element._path = 'chell.go';
- flush();
+ element.path = 'chell.go';
+ await element.updateComplete;
assert.equal(linkEls[0].getAttribute('href'), '42-10-5');
assert.equal(linkEls[1].getAttribute('href'), '42-10-5');
assert.equal(linkEls[2].getAttribute('href'), '42-glados.txt-10-5');
});
});
- test('_handlePatchChange calls navigateToDiff correctly', () => {
+ test('handlePatchChange calls navigateToDiff correctly', async () => {
const navigateStub = sinon.stub(GerritNav, 'navigateToDiff');
- element._change = {
+ element.change = {
...createParsedChange(),
_number: 321 as NumericChangeId,
project: 'foo/bar' as RepoName,
};
- element._path = 'path/to/file.txt';
+ element.path = 'path/to/file.txt';
- element._patchRange = {
+ element.patchRange = {
basePatchNum: PARENT,
patchNum: 3 as RevisionPatchSetNum,
};
+ await element.updateComplete;
const detail = {
basePatchNum: PARENT,
patchNum: 1 as RevisionPatchSetNum,
};
- element.$.rangeSelect.dispatchEvent(
+ queryAndAssert(element, '#rangeSelect').dispatchEvent(
new CustomEvent('patch-range-change', {detail, bubbles: false})
);
assert(
navigateStub.lastCall.calledWithExactly(
- element._change,
- element._path,
+ element.change,
+ element.path,
1 as RevisionPatchSetNum,
PARENT
)
@@ -1451,8 +1468,9 @@
const setReviewedStatusStub = sinon.spy(element, 'setReviewedStatus');
- sinon.stub(element.$.diffHost, 'reload');
- sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload');
+ element.loggedIn = true;
const diffPreferences = {
...createDefaultDiffPrefs(),
manual_review: true,
@@ -1470,7 +1488,7 @@
view: GerritView.DIFF,
patchNum: 2 as RevisionPatchSetNum,
});
- element._patchRange = {
+ element.patchRange = {
patchNum: 2 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
@@ -1482,7 +1500,7 @@
// if prefs are updated then the reviewed status should not be set again
element.userModel.setDiffPreferences(createDefaultDiffPrefs());
- await flush();
+ await element.updateComplete;
assert.isFalse(setReviewedFileStatusStub.called);
}
);
@@ -1492,8 +1510,9 @@
.stub(element.getChangeModel(), 'setReviewedFilesStatus')
.callsFake(() => Promise.resolve());
- sinon.stub(element.$.diffHost, 'reload');
- sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload');
+ element.loggedIn = true;
const diffPreferences = {
...createDefaultDiffPrefs(),
manual_review: false,
@@ -1511,7 +1530,7 @@
view: GerritView.DIFF,
patchNum: 22 as RevisionPatchSetNum,
});
- element._patchRange = {
+ element.patchRange = {
patchNum: 2 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
@@ -1528,11 +1547,12 @@
reviewedFiles: [],
loadingStatus: LoadingStatus.LOADED,
});
- sinon.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
+ element.loggedIn = true;
const saveReviewedStub = sinon
.stub(element.getChangeModel(), 'setReviewedFilesStatus')
.callsFake(() => Promise.resolve());
- sinon.stub(element.$.diffHost, 'reload');
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload');
element.userModel.setDiffPreferences(createDefaultDiffPrefs());
@@ -1542,7 +1562,7 @@
patchNum: 2 as RevisionPatchSetNum,
});
- element._patchRange = {
+ element.patchRange = {
patchNum: 2 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
@@ -1550,7 +1570,7 @@
await waitUntil(() => saveReviewedStub.called);
element.getChangeModel().updateStateFileReviewed('/COMMIT_MSG', true);
- await flush();
+ await element.updateComplete;
const reviewedStatusCheckBox = queryAndAssert<HTMLInputElement>(
element,
@@ -1575,7 +1595,7 @@
]);
element.getChangeModel().updateStateFileReviewed('/COMMIT_MSG', false);
- await flush();
+ await element.updateComplete;
MockInteractions.tap(reviewedStatusCheckBox);
assert.isTrue(reviewedStatusCheckBox.checked);
@@ -1588,8 +1608,12 @@
const callCount = saveReviewedStub.callCount;
- element.set('params.view', GerritNav.View.CHANGE);
- await flush();
+ element.params = {
+ view: GerritNav.View.CHANGE,
+ changeNum: 42 as NumericChangeId,
+ project: 'test' as RepoName,
+ };
+ await element.updateComplete;
// saveReviewedState observer observes params, but should not fire when
// view !== GerritNav.View.DIFF.
@@ -1602,22 +1626,23 @@
'setReviewedFilesStatus'
);
- element._patchRange = {
+ element.patchRange = {
basePatchNum: 1 as BasePatchSetNum,
patchNum: EDIT,
};
flush();
- assert.isTrue(element._editMode);
+ assert.isTrue(element.computeEditMode());
element.setReviewed(true);
assert.isFalse(saveReviewedStub.called);
});
test('hash is determined from params', async () => {
- sinon.stub(element.$.diffHost, 'reload');
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload');
const initLineStub = sinon.stub(element, 'initLineOfInterestAndCursor');
- element._loggedIn = true;
+ element.loggedIn = true;
element.params = {
view: GerritNav.View.DIFF,
changeNum: 42 as NumericChangeId,
@@ -1626,14 +1651,16 @@
path: '/COMMIT_MSG',
};
+ await element.updateComplete;
await flush();
assert.isTrue(initLineStub.calledOnce);
});
- test('diff mode selector correctly toggles the diff', () => {
- const select = element.$.modeSelect;
- const diffDisplay = element.$.diffHost;
- element._userPrefs = {
+ test('diff mode selector correctly toggles the diff', async () => {
+ const select = queryAndAssert<GrDiffModeSelector>(element, '#modeSelect');
+ const diffDisplay = element.diffHost;
+ assertIsDefined(diffDisplay);
+ element.userPrefs = {
...createDefaultPreferences(),
diff_view: DiffViewMode.SIDE_BY_SIDE,
};
@@ -1641,9 +1668,9 @@
const userStub = stubUsers('updatePreferences');
- flush();
+ await element.updateComplete;
// The mode selected in the view state reflects the selected option.
- // assert.equal(element._userPrefs.diff_view, select.mode);
+ // assert.equal(element.userPrefs.diff_view, select.mode);
// The mode selected in the view state reflects the view rednered in the
// diff.
@@ -1659,18 +1686,18 @@
});
test('diff mode selector should be hidden for binary', async () => {
- element._diff = {
+ element.diff = {
...createDiff(),
binary: true,
content: [],
};
- await flush();
+ await element.updateComplete;
const diffModeSelector = queryAndAssert(element, '.diffModeSelector');
assert.isTrue(diffModeSelector.classList.contains('hide'));
});
- suite('_commitRange', () => {
+ suite('commitRange', () => {
const change: ParsedChangeInfo = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -1695,11 +1722,12 @@
},
};
setup(async () => {
- sinon.stub(element.$.diffHost, 'reload');
+ assertIsDefined(element.diffHost);
+ sinon.stub(element.diffHost, 'reload');
sinon.stub(element, 'initCursor');
- element._change = change;
- await flush();
- await element.$.diffHost.updateComplete;
+ element.change = change;
+ await element.updateComplete;
+ await element.diffHost.updateComplete;
});
test('uses the patchNum and basePatchNum ', async () => {
@@ -1710,10 +1738,10 @@
basePatchNum: 2 as BasePatchSetNum,
path: '/COMMIT_MSG',
};
- element._change = change;
+ element.change = change;
+ await element.updateComplete;
await flush();
- await element.$.diffHost.updateComplete;
- assert.deepEqual(element._commitRange, {
+ assert.deepEqual(element.commitRange, {
baseCommit: 'commit-sha-2' as CommitId,
commit: 'commit-sha-4' as CommitId,
});
@@ -1726,10 +1754,10 @@
patchNum: 5 as RevisionPatchSetNum,
path: '/COMMIT_MSG',
};
- element._change = change;
+ element.change = change;
+ await element.updateComplete;
await flush();
- await element.$.diffHost.updateComplete;
- assert.deepEqual(element._commitRange, {
+ assert.deepEqual(element.commitRange, {
commit: 'commit-sha-5' as CommitId,
baseCommit: 'sha-5-parent' as CommitId,
});
@@ -1750,40 +1778,40 @@
// Revision hash: specifies lineNum but not side.
- element._focusLineNum = 234;
+ element.focusLineNum = 234;
element.initCursor(false);
assert.equal(element.cursor.initialLineNumber, 234);
assert.equal(element.cursor.side, Side.RIGHT);
// Base hash: specifies lineNum and side.
- element._focusLineNum = 345;
+ element.focusLineNum = 345;
element.initCursor(true);
assert.equal(element.cursor.initialLineNumber, 345);
assert.equal(element.cursor.side, Side.LEFT);
// Specifies right side:
- element._focusLineNum = 123;
+ element.focusLineNum = 123;
element.initCursor(false);
assert.equal(element.cursor.initialLineNumber, 123);
assert.equal(element.cursor.side, Side.RIGHT);
});
- test('_getLineOfInterest', () => {
- assert.isUndefined(element._getLineOfInterest(false));
+ test('getLineOfInterest', () => {
+ assert.isUndefined(element.getLineOfInterest(false));
- element._focusLineNum = 12;
- let result = element._getLineOfInterest(false);
+ element.focusLineNum = 12;
+ let result = element.getLineOfInterest(false);
assert.isOk(result);
assert.equal(result!.lineNum, 12);
assert.equal(result!.side, Side.RIGHT);
- result = element._getLineOfInterest(true);
+ result = element.getLineOfInterest(true);
assert.isOk(result);
assert.equal(result!.lineNum, 12);
assert.equal(result!.side, Side.LEFT);
});
- test('_onLineSelected', () => {
+ test('onLineSelected', () => {
const getUrlStub = sinon.stub(GerritNav, 'getUrlForDiffById');
const replaceStateStub = sinon.stub(history, 'replaceState');
assertIsDefined(element.cursor);
@@ -1791,20 +1819,19 @@
.stub(element.cursor, 'getAddress')
.returns({number: 123, leftSide: false});
- element._changeNum = 321 as NumericChangeId;
- element._change = {
+ element.changeNum = 321 as NumericChangeId;
+ element.change = {
...createParsedChange(),
_number: 321 as NumericChangeId,
project: 'foo/bar' as RepoName,
};
- element._patchRange = {
+ element.patchRange = {
basePatchNum: 3 as BasePatchSetNum,
patchNum: 5 as RevisionPatchSetNum,
};
- const e = {} as CustomEvent;
- const detail = {number: 123, side: Side.RIGHT};
+ const e = {detail: {number: 123, side: Side.RIGHT}} as CustomEvent;
- element._onLineSelected(e, detail);
+ element.onLineSelected(e);
assert.isTrue(replaceStateStub.called);
assert.isTrue(getUrlStub.called);
@@ -1819,20 +1846,19 @@
.stub(element.cursor, 'getAddress')
.returns({number: 123, leftSide: true});
- element._changeNum = 321 as NumericChangeId;
- element._change = {
+ element.changeNum = 321 as NumericChangeId;
+ element.change = {
...createParsedChange(),
_number: 321 as NumericChangeId,
project: 'foo/bar' as RepoName,
};
- element._patchRange = {
+ element.patchRange = {
basePatchNum: 3 as BasePatchSetNum,
patchNum: 5 as RevisionPatchSetNum,
};
- const e = {} as CustomEvent;
- const detail = {number: 123, side: Side.LEFT};
+ const e = {detail: {number: 123, side: Side.LEFT}} as CustomEvent;
- element._onLineSelected(e, detail);
+ element.onLineSelected(e);
assert.isTrue(replaceStateStub.called);
assert.isTrue(getUrlStub.called);
@@ -1841,7 +1867,7 @@
test('handleToggleDiffMode', () => {
const userStub = stubUsers('updatePreferences');
- element._userPrefs = {
+ element.userPrefs = {
...createDefaultPreferences(),
diff_view: DiffViewMode.SIDE_BY_SIDE,
};
@@ -1851,7 +1877,7 @@
diff_view: DiffViewMode.UNIFIED,
});
- element._userPrefs = {
+ element.userPrefs = {
...createDefaultPreferences(),
diff_view: DiffViewMode.UNIFIED,
};
@@ -1871,41 +1897,41 @@
patchNum: 3 as RevisionPatchSetNum,
path: 'abcd',
};
- await flush();
+ await element.updateComplete;
});
test('empty', () => {
- sinon.stub(element, '_getPaths').returns({});
+ sinon.stub(element, 'getPaths').returns({});
element.initPatchRange();
- assert.equal(Object.keys(element._commentMap ?? {}).length, 0);
+ assert.equal(Object.keys(element.commentMap ?? {}).length, 0);
});
test('has paths', () => {
- sinon.stub(element, '_getFiles');
- sinon.stub(element, '_getPaths').returns({
+ sinon.stub(element, 'fetchFiles');
+ sinon.stub(element, 'getPaths').returns({
'path/to/file/one.cpp': true,
'path-to/file/two.py': true,
});
- element._changeNum = 42 as NumericChangeId;
- element._patchRange = {
+ element.changeNum = 42 as NumericChangeId;
+ element.patchRange = {
basePatchNum: 3 as BasePatchSetNum,
patchNum: 5 as RevisionPatchSetNum,
};
element.initPatchRange();
- assert.deepEqual(Object.keys(element._commentMap ?? {}), [
+ assert.deepEqual(Object.keys(element.commentMap ?? {}), [
'path/to/file/one.cpp',
'path-to/file/two.py',
]);
});
});
- suite('_computeCommentSkips', () => {
+ suite('computeCommentSkips', () => {
test('empty file list', () => {
const commentMap = {
'path/one.jpg': true,
'path/three.wav': true,
};
const path = 'path/two.m4v';
- const result = element._computeCommentSkips(commentMap, [], path);
+ const result = element.computeCommentSkips(commentMap, [], path);
assert.isOk(result);
assert.isNotOk(result!.previous);
assert.isNotOk(result!.next);
@@ -1919,28 +1945,28 @@
commentMap[fileList[1]] = false;
commentMap[fileList[2]] = true;
- let result = element._computeCommentSkips(commentMap, fileList, path);
+ let result = element.computeCommentSkips(commentMap, fileList, path);
assert.isOk(result);
assert.equal(result!.previous, fileList[0]);
assert.equal(result!.next, fileList[2]);
commentMap[fileList[1]] = true;
- result = element._computeCommentSkips(commentMap, fileList, path);
+ result = element.computeCommentSkips(commentMap, fileList, path);
assert.isOk(result);
assert.equal(result!.previous, fileList[0]);
assert.equal(result!.next, fileList[2]);
path = fileList[0];
- result = element._computeCommentSkips(commentMap, fileList, path);
+ result = element.computeCommentSkips(commentMap, fileList, path);
assert.isOk(result);
assert.isNull(result!.previous);
assert.equal(result!.next, fileList[1]);
path = fileList[2];
- result = element._computeCommentSkips(commentMap, fileList, path);
+ result = element.computeCommentSkips(commentMap, fileList, path);
assert.isOk(result);
assert.equal(result!.previous, fileList[1]);
assert.isNull(result!.next);
@@ -1953,12 +1979,12 @@
setup(() => {
navToChangeStub = sinon.stub(element, 'navToChangeView');
navToDiffStub = sinon.stub(GerritNav, 'navigateToDiff');
- element._files = getFilesFromFileList([
+ element.files = getFilesFromFileList([
'path/one.jpg',
'path/two.m4v',
'path/three.wav',
]);
- element._patchRange = {
+ element.patchRange = {
patchNum: 2 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
};
@@ -1971,26 +1997,28 @@
assert.isFalse(navToDiffStub.called);
});
- test('no previous', () => {
+ test('no previous', async () => {
const commentMap: CommentMap = {};
- commentMap[element._fileList![0]!] = false;
- commentMap[element._fileList![1]!] = false;
- commentMap[element._fileList![2]!] = true;
- element._commentMap = commentMap;
- element._path = element._fileList![1]!;
+ commentMap[element.files.sortedFileList[0]!] = false;
+ commentMap[element.files.sortedFileList[1]!] = false;
+ commentMap[element.files.sortedFileList[2]!] = true;
+ element.commentMap = commentMap;
+ element.path = element.files.sortedFileList[1];
+ await element.updateComplete;
element.moveToPreviousFileWithComment();
assert.isTrue(navToChangeStub.calledOnce);
assert.isFalse(navToDiffStub.called);
});
- test('w/ previous', () => {
+ test('w/ previous', async () => {
const commentMap: CommentMap = {};
- commentMap[element._fileList![0]!] = true;
- commentMap[element._fileList![1]!] = false;
- commentMap[element._fileList![2]!] = true;
- element._commentMap = commentMap;
- element._path = element._fileList![1]!;
+ commentMap[element.files.sortedFileList[0]!] = true;
+ commentMap[element.files.sortedFileList[1]!] = false;
+ commentMap[element.files.sortedFileList[2]!] = true;
+ element.commentMap = commentMap;
+ element.path = element.files.sortedFileList[1];
+ await element.updateComplete;
element.moveToPreviousFileWithComment();
assert.isFalse(navToChangeStub.called);
@@ -2005,26 +2033,28 @@
assert.isFalse(navToDiffStub.called);
});
- test('no previous', () => {
+ test('no previous', async () => {
const commentMap: CommentMap = {};
- commentMap[element._fileList![0]!] = true;
- commentMap[element._fileList![1]!] = false;
- commentMap[element._fileList![2]!] = false;
- element._commentMap = commentMap;
- element._path = element._fileList![1];
+ commentMap[element.files.sortedFileList[0]!] = true;
+ commentMap[element.files.sortedFileList[1]!] = false;
+ commentMap[element.files.sortedFileList[2]!] = false;
+ element.commentMap = commentMap;
+ element.path = element.files.sortedFileList[1];
+ await element.updateComplete;
element.moveToNextFileWithComment();
assert.isTrue(navToChangeStub.calledOnce);
assert.isFalse(navToDiffStub.called);
});
- test('w/ previous', () => {
+ test('w/ previous', async () => {
const commentMap: CommentMap = {};
- commentMap[element._fileList![0]!] = true;
- commentMap[element._fileList![1]!] = false;
- commentMap[element._fileList![2]!] = true;
- element._commentMap = commentMap;
- element._path = element._fileList![1];
+ commentMap[element.files.sortedFileList[0]!] = true;
+ commentMap[element.files.sortedFileList[1]!] = false;
+ commentMap[element.files.sortedFileList[2]!] = true;
+ element.commentMap = commentMap;
+ element.path = element.files.sortedFileList[1];
+ await element.updateComplete;
element.moveToNextFileWithComment();
assert.isFalse(navToChangeStub.called);
@@ -2035,8 +2065,10 @@
});
test('_computeEditMode', () => {
- const callCompute = (range: PatchRange) =>
- element._computeEditMode({base: range, path: '', value: range});
+ const callCompute = (range: PatchRange) => {
+ element.patchRange = range;
+ return element.computeEditMode();
+ };
assert.isFalse(
callCompute({
basePatchNum: PARENT,
@@ -2051,16 +2083,18 @@
);
});
- test('_computeFileNum', () => {
+ test('computeFileNum', () => {
+ element.path = '/foo';
assert.equal(
- element._computeFileNum('/foo', [
+ element.computeFileNum([
{text: '/foo', value: '/foo'},
{text: '/bar', value: '/bar'},
]),
1
);
+ element.path = '/bar';
assert.equal(
- element._computeFileNum('/bar', [
+ element.computeFileNum([
{text: '/foo', value: '/foo'},
{text: '/bar', value: '/bar'},
]),
@@ -2068,10 +2102,10 @@
);
});
- test('_computeFileNumClass', () => {
- assert.equal(element._computeFileNumClass(0, []), '');
+ test('computeFileNumClass', () => {
+ assert.equal(element.computeFileNumClass(0, []), '');
assert.equal(
- element._computeFileNumClass(1, [
+ element.computeFileNumClass(1, [
{text: '/foo', value: '/foo'},
{text: '/bar', value: '/bar'},
]),
@@ -2079,24 +2113,27 @@
);
});
- test('f open file dropdown', () => {
- assert.isFalse(element.$.dropdown.$.dropdown.opened);
+ test('f open file dropdown', async () => {
+ assertIsDefined(element.dropdown);
+ assert.isFalse(element.dropdown.$.dropdown.opened);
MockInteractions.pressAndReleaseKeyOn(element, 70, null, 'f');
- flush();
- assert.isTrue(element.$.dropdown.$.dropdown.opened);
+ await element.updateComplete;
+ assert.isTrue(element.dropdown.$.dropdown.opened);
});
suite('blame', () => {
test('toggle blame with button', () => {
+ assertIsDefined(element.diffHost);
const toggleBlame = sinon
- .stub(element.$.diffHost, 'loadBlame')
+ .stub(element.diffHost, 'loadBlame')
.callsFake(() => Promise.resolve([]));
- MockInteractions.tap(element.$.toggleBlame);
+ MockInteractions.tap(queryAndAssert(element, '#toggleBlame'));
assert.isTrue(toggleBlame.calledOnce);
});
test('toggle blame with shortcut', () => {
+ assertIsDefined(element.diffHost);
const toggleBlame = sinon
- .stub(element.$.diffHost, 'loadBlame')
+ .stub(element.diffHost, 'loadBlame')
.callsFake(() => Promise.resolve([]));
MockInteractions.pressAndReleaseKeyOn(element, 66, null, 'b');
assert.isTrue(toggleBlame.calledOnce);
@@ -2104,19 +2141,22 @@
});
suite('editMode behavior', () => {
- setup(() => {
- element._loggedIn = true;
+ setup(async () => {
+ element.loggedIn = true;
+ await element.updateComplete;
});
- test('reviewed checkbox', () => {
- sinon.stub(element, '_handlePatchChange');
- element._patchRange = createPatchRange();
+ test('reviewed checkbox', async () => {
+ sinon.stub(element, 'handlePatchChange');
+ element.patchRange = createPatchRange();
+ await element.updateComplete;
+ assertIsDefined(element.reviewed);
// Reviewed checkbox should be shown.
- assert.isTrue(isVisible(element.$.reviewed));
- element.set('_patchRange.patchNum', EDIT);
- flush();
+ assert.isTrue(isVisible(element.reviewed));
+ element.patchRange = {...element.patchRange, patchNum: EDIT};
+ await element.updateComplete;
- assert.isFalse(isVisible(element.$.reviewed));
+ assert.isFalse(isVisible(element.reviewed));
});
});
@@ -2158,9 +2198,9 @@
moveToNextChunkStub.returns(CursorMoveResult.CLIPPED);
isAtEndStub.returns(true);
- element._files = getFilesFromFileList(['file1', 'file2', 'file3']);
+ element.files = getFilesFromFileList(['file1', 'file2', 'file3']);
element.reviewedFiles = new Set(['file2']);
- element._path = 'file1';
+ element.path = 'file1';
nowStub.returns(5);
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
@@ -2168,11 +2208,7 @@
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
assert.isTrue(navToFileStub.called);
- assert.deepEqual(navToFileStub.lastCall.args, [
- 'file1',
- ['file1', 'file3'],
- 1,
- ]);
+ assert.deepEqual(navToFileStub.lastCall.args, [['file1', 'file3'], 1]);
});
test('does not navigate if n is tapped twice too slow', () => {
@@ -2202,9 +2238,9 @@
moveToPreviousChunkStub.returns(CursorMoveResult.CLIPPED);
isAtStartStub.returns(true);
- element._files = getFilesFromFileList(['file1', 'file2', 'file3']);
+ element.files = getFilesFromFileList(['file1', 'file2', 'file3']);
element.reviewedFiles = new Set(['file2']);
- element._path = 'file3';
+ element.path = 'file3';
nowStub.returns(5);
MockInteractions.pressAndReleaseKeyOn(element, 80, null, 'p');
@@ -2212,11 +2248,7 @@
MockInteractions.pressAndReleaseKeyOn(element, 80, null, 'p');
assert.isTrue(navToFileStub.called);
- assert.deepEqual(navToFileStub.lastCall.args, [
- 'file3',
- ['file1', 'file3'],
- -1,
- ]);
+ assert.deepEqual(navToFileStub.lastCall.args, [['file1', 'file3'], -1]);
});
test('does not navigate if p is tapped twice too slow', () => {
@@ -2249,20 +2281,20 @@
});
test('shift+m navigates to next unreviewed file', () => {
- element._files = getFilesFromFileList(['file1', 'file2', 'file3']);
+ element.files = getFilesFromFileList(['file1', 'file2', 'file3']);
element.reviewedFiles = new Set(['file1', 'file2']);
- element._path = 'file1';
+ element.path = 'file1';
const reviewedStub = sinon.stub(element, 'setReviewed');
const navStub = sinon.stub(element, 'navToFile');
MockInteractions.pressAndReleaseKeyOn(element, 77, null, 'M');
flush();
assert.isTrue(reviewedStub.lastCall.args[0]);
- assert.deepEqual(navStub.lastCall.args, ['file1', ['file1', 'file3'], 1]);
+ assert.deepEqual(navStub.lastCall.args, [['file1', 'file3'], 1]);
});
test('File change should trigger navigateToDiff once', async () => {
- element._files = getFilesFromFileList(['file1', 'file2', 'file3']);
+ element.files = getFilesFromFileList(['file1', 'file2', 'file3']);
sinon.stub(element, 'initLineOfInterestAndCursor');
const navigateToDiffStub = sinon.stub(GerritNav, 'navigateToDiff');
@@ -2274,19 +2306,19 @@
project: 'test-project' as RepoName,
path: 'file1',
};
- element._patchRange = {
+ element.patchRange = {
patchNum: 1 as RevisionPatchSetNum,
basePatchNum: PARENT,
};
- element._change = {
+ element.change = {
...createParsedChange(),
revisions: createRevisions(1),
};
- await flush();
+ await element.updateComplete;
assert.isTrue(navigateToDiffStub.notCalled);
// Switch to file2
- element._handleFileChange(
+ element.handleFileChange(
new CustomEvent('value-change', {detail: {value: 'file2'}})
);
assert.isTrue(navigateToDiffStub.calledOnce);
@@ -2299,7 +2331,7 @@
project: 'test-project' as RepoName,
path: 'file2',
};
- element._patchRange = {
+ element.patchRange = {
patchNum: 1 as RevisionPatchSetNum,
basePatchNum: PARENT,
};
@@ -2324,21 +2356,16 @@
},
];
- const base = {
+ element.change = createParsedChange();
+ element.change.project = 'test' as RepoName;
+ element.changeNum = 12 as NumericChangeId;
+ element.patchRange = {
patchNum: 1 as RevisionPatchSetNum,
basePatchNum: PARENT,
};
-
- assert.deepEqual(
- element._computeDownloadDropdownLinks(
- 'test' as RepoName,
- 12 as NumericChangeId,
- base,
- 'index.php',
- createDiff()
- ),
- downloadLinks
- );
+ element.path = 'index.php';
+ element.diff = createDiff();
+ assert.deepEqual(element.computeDownloadDropdownLinks(), downloadLinks);
});
test('_computeDownloadDropdownLinks diff returns renamed', () => {
@@ -2361,26 +2388,21 @@
diff.change_type = 'RENAMED';
diff.meta_a!.name = 'index2.php';
- const base = {
+ element.change = createParsedChange();
+ element.change.project = 'test' as RepoName;
+ element.changeNum = 12 as NumericChangeId;
+ element.patchRange = {
patchNum: 3 as RevisionPatchSetNum,
basePatchNum: 2 as BasePatchSetNum,
};
-
- assert.deepEqual(
- element._computeDownloadDropdownLinks(
- 'test' as RepoName,
- 12 as NumericChangeId,
- base,
- 'index.php',
- diff
- ),
- downloadLinks
- );
+ element.path = 'index.php';
+ element.diff = diff;
+ assert.deepEqual(element.computeDownloadDropdownLinks(), downloadLinks);
});
- test('_computeDownloadFileLink', () => {
+ test('computeDownloadFileLink', () => {
assert.equal(
- element._computeDownloadFileLink(
+ element.computeDownloadFileLink(
'test' as RepoName,
12 as NumericChangeId,
{patchNum: 1 as PatchSetNumber, basePatchNum: PARENT},
@@ -2391,7 +2413,7 @@
);
assert.equal(
- element._computeDownloadFileLink(
+ element.computeDownloadFileLink(
'test' as RepoName,
12 as NumericChangeId,
{patchNum: 1 as PatchSetNumber, basePatchNum: -2 as PatchSetNumber},
@@ -2402,7 +2424,7 @@
);
assert.equal(
- element._computeDownloadFileLink(
+ element.computeDownloadFileLink(
'test' as RepoName,
12 as NumericChangeId,
{patchNum: 3 as PatchSetNumber, basePatchNum: 2 as PatchSetNumber},
@@ -2413,7 +2435,7 @@
);
assert.equal(
- element._computeDownloadFileLink(
+ element.computeDownloadFileLink(
'test' as RepoName,
12 as NumericChangeId,
{patchNum: 3 as PatchSetNumber, basePatchNum: 2 as PatchSetNumber},
@@ -2424,9 +2446,9 @@
);
});
- test('_computeDownloadPatchLink', () => {
+ test('computeDownloadPatchLink', () => {
assert.equal(
- element._computeDownloadPatchLink(
+ element.computeDownloadPatchLink(
'test' as RepoName,
12 as NumericChangeId,
{basePatchNum: PARENT, patchNum: 1 as RevisionPatchSetNum},
@@ -2453,39 +2475,31 @@
stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
stubRestApi('getReviewedFiles').returns(Promise.resolve([]));
element = basicFixture.instantiate();
- element._changeNum = 42 as NumericChangeId;
+ element.changeNum = 42 as NumericChangeId;
});
- test('_getFiles add files with comments without changes', () => {
- const patchChangeRecord = {
- base: {
- basePatchNum: 5 as BasePatchSetNum,
- patchNum: 10 as RevisionPatchSetNum,
- },
- value: {
- basePatchNum: 5 as BasePatchSetNum,
- patchNum: 10 as RevisionPatchSetNum,
- },
- path: '',
+ test('fetchFiles add files with comments without changes', () => {
+ element.patchRange = {
+ basePatchNum: 5 as BasePatchSetNum,
+ patchNum: 10 as RevisionPatchSetNum,
};
- const changeComments = {
+ element.changeComments = {
getPaths: sinon.stub().returns({
'file2.txt': {},
'file1.txt': {},
}),
} as unknown as ChangeComments;
- return element
- ._getFiles(23 as NumericChangeId, patchChangeRecord, changeComments)
- .then(() => {
- assert.deepEqual(element._files, {
- sortedFileList: ['a/b/test.c', 'file1.txt', 'file2.txt'],
- changeFilesByPath: {
- 'file1.txt': createFileInfo(),
- 'file2.txt': {status: 'U'} as FileInfo,
- 'a/b/test.c': createFileInfo(),
- },
- });
+ element.changeNum = 23 as NumericChangeId;
+ return element.fetchFiles().then(() => {
+ assert.deepEqual(element.files, {
+ sortedFileList: ['a/b/test.c', 'file1.txt', 'file2.txt'],
+ changeFilesByPath: {
+ 'file1.txt': createFileInfo(),
+ 'file2.txt': {status: 'U'} as FileInfo,
+ 'a/b/test.c': createFileInfo(),
+ },
});
+ });
});
});
});
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index a39d310..4899501 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -134,7 +134,7 @@
}
override disconnectedCallback() {
- this.storeTask?.cancel();
+ this.storeTask?.flush();
for (const cleanup of this.cleanups) cleanup();
this.cleanups = [];
super.disconnectedCallback();
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 15ff34e..ce2aff2 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -26,11 +26,10 @@
import './settings/gr-registration-dialog/gr-registration-dialog';
import './settings/gr-settings-view/gr-settings-view';
import {getBaseUrl} from '../utils/url-util';
-import {Shortcut} from '../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {GerritNav} from './core/gr-navigation/gr-navigation';
import {getAppContext} from '../services/app-context';
import {GrRouter} from './core/gr-router/gr-router';
-import {AccountDetailInfo, ServerInfo} from '../types/common';
+import {AccountDetailInfo} from '../types/common';
import {
constructServerErrorMsg,
GrErrorManager,
@@ -62,7 +61,7 @@
import {sharedStyles} from '../styles/shared-styles';
import {LitElement, PropertyValues, html, css, nothing} from 'lit';
import {customElement, property, query, state} from 'lit/decorators';
-import {ShortcutController} from './lit/shortcut-controller';
+import {Shortcut, ShortcutController} from './lit/shortcut-controller';
import {cache} from 'lit/directives/cache';
import {assertIsDefined} from '../utils/common-util';
import './gr-css-mixins';
@@ -101,8 +100,6 @@
@state() private account?: AccountDetailInfo;
- @state() private serverConfig?: ServerInfo;
-
@state() private version?: string;
@state() private showChangeListView?: boolean;
@@ -226,9 +223,6 @@
this.reporting.reportLifeCycle(LifeCycle.STARTED_AS_GUEST);
}
});
- this.restApiService.getConfig().then(config => {
- this.serverConfig = config;
- });
this.restApiService.getVersion().then(version => {
this.version = version;
this.logWelcome();
@@ -376,8 +370,7 @@
id="errorManager"
.loginUrl=${this.loginUrl}
></gr-error-manager>
- <gr-plugin-host id="plugins" .config=${this.serverConfig}>
- </gr-plugin-host>
+ <gr-plugin-host id="plugins"></gr-plugin-host>
<gr-external-style
id="externalStyleForAll"
name="app-theme"
@@ -396,7 +389,6 @@
id="search"
label="Search for changes"
.searchQuery=${(this.params as AppElementSearchParam)?.query}
- .serverConfig=${this.serverConfig}
>
</gr-smart-search>
`;
diff --git a/polygerrit-ui/app/elements/gr-app_test.ts b/polygerrit-ui/app/elements/gr-app_test.ts
index 06fce4d..2b84344 100644
--- a/polygerrit-ui/app/elements/gr-app_test.ts
+++ b/polygerrit-ui/app/elements/gr-app_test.ts
@@ -16,7 +16,6 @@
createServerInfo,
} from '../test/test-data-generators';
import {GrAppElement} from './gr-app-element';
-import {GrPluginHost} from './plugins/gr-plugin-host/gr-plugin-host';
import {GrRouter} from './core/gr-router/gr-router';
suite('gr-app tests', () => {
@@ -48,12 +47,6 @@
sinon.assert.callOrder(appStartedStub, routerStartStub);
});
- test('passes config to gr-plugin-host', () => {
- const grAppElement = queryAndAssert<GrAppElement>(grApp, '#app-element');
- const pluginHost = queryAndAssert<GrPluginHost>(grAppElement, '#plugins');
- assert.deepEqual(pluginHost.config, config);
- });
-
test('_paramsChanged sets search page', () => {
const grAppElement = queryAndAssert<GrAppElement>(grApp, '#app-element');
diff --git a/polygerrit-ui/app/elements/lit/shortcut-controller.ts b/polygerrit-ui/app/elements/lit/shortcut-controller.ts
index 9d39cd7..b315529 100644
--- a/polygerrit-ui/app/elements/lit/shortcut-controller.ts
+++ b/polygerrit-ui/app/elements/lit/shortcut-controller.ts
@@ -9,6 +9,7 @@
import {Shortcut} from '../../services/shortcuts/shortcuts-config';
import {resolve} from '../../models/dependency';
+export {Shortcut};
interface ShortcutListener {
binding: Binding;
listener: (e: KeyboardEvent) => void;
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
index 9b62743..dfdf12c 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.ts
@@ -3,27 +3,37 @@
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {LitElement, PropertyValues} from 'lit';
-import {customElement, property} from 'lit/decorators';
+import {LitElement} from 'lit';
+import {customElement, state} from 'lit/decorators';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {ServerInfo} from '../../../types/common';
+import {subscribe} from '../../lit/subscription-controller';
+import {resolve} from '../../../models/dependency';
+import {configModelToken} from '../../../models/config/config-model';
@customElement('gr-plugin-host')
export class GrPluginHost extends LitElement {
- @property({type: Object})
+ @state()
config?: ServerInfo;
- _configChanged(config: ServerInfo) {
- const jsPlugins = config.plugin?.js_resource_paths ?? [];
- const themes: string[] = config.default_theme ? [config.default_theme] : [];
- const instanceId = config.gerrit?.instance_id;
- getPluginLoader().loadPlugins([...themes, ...jsPlugins], instanceId);
- }
+ // visible for testing
+ readonly getConfigModel = resolve(this, configModelToken);
- override updated(changedProperties: PropertyValues<GrPluginHost>) {
- if (changedProperties.has('config') && this.config) {
- this._configChanged(this.config);
- }
+ constructor() {
+ super();
+ subscribe(
+ this,
+ () => this.getConfigModel().serverConfig$,
+ config => {
+ if (!config) return;
+ const jsPlugins = config?.plugin?.js_resource_paths ?? [];
+ const themes: string[] = config?.default_theme
+ ? [config.default_theme]
+ : [];
+ const instanceId = config?.gerrit?.instance_id;
+ getPluginLoader().loadPlugins([...themes, ...jsPlugins], instanceId);
+ }
+ );
}
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.ts b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.ts
index 945af72..756221e 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.ts
@@ -8,28 +8,32 @@
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {GrPluginHost} from './gr-plugin-host';
import {fixture, html} from '@open-wc/testing-helpers';
-import {ServerInfo} from '../../../api/rest-api';
+import {SinonStub} from 'sinon';
+import {createServerInfo} from '../../../test/test-data-generators';
suite('gr-plugin-host tests', () => {
let element: GrPluginHost;
+ let loadPluginsStub: SinonStub;
setup(async () => {
+ loadPluginsStub = sinon.stub(getPluginLoader(), 'loadPlugins');
element = await fixture<GrPluginHost>(html`
<gr-plugin-host></gr-plugin-host>
`);
+ await element.updateComplete;
sinon.stub(document.body, 'appendChild');
});
test('load plugins should be called', async () => {
- const loadPluginsStub = sinon.stub(getPluginLoader(), 'loadPlugins');
- element.config = {
+ loadPluginsStub.reset();
+ element.getConfigModel().updateServerConfig({
+ ...createServerInfo(),
plugin: {
has_avatars: false,
js_resource_paths: ['plugins/42', 'plugins/foo/bar', 'plugins/baz'],
},
- } as ServerInfo;
- await flush();
+ });
assert.isTrue(loadPluginsStub.calledOnce);
assert.isTrue(
loadPluginsStub.calledWith([
@@ -41,15 +45,15 @@
});
test('theme plugins should be loaded if enabled', async () => {
- const loadPluginsStub = sinon.stub(getPluginLoader(), 'loadPlugins');
- element.config = {
+ loadPluginsStub.reset();
+ element.getConfigModel().updateServerConfig({
+ ...createServerInfo(),
default_theme: 'gerrit-theme.js',
plugin: {
has_avatars: false,
js_resource_paths: ['plugins/42', 'plugins/foo/bar', 'plugins/baz'],
},
- } as ServerInfo;
- await flush();
+ });
assert.isTrue(loadPluginsStub.calledOnce);
assert.isTrue(
loadPluginsStub.calledWith([
@@ -60,4 +64,13 @@
])
);
});
+
+ test('plugins loaded with instanceId ', async () => {
+ loadPluginsStub.reset();
+ const config = createServerInfo();
+ config.gerrit.instance_id = 'test-id';
+ element.getConfigModel().updateServerConfig(config);
+ assert.isTrue(loadPluginsStub.calledOnce);
+ assert.isTrue(loadPluginsStub.calledWith([], 'test-id'));
+ });
});
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
index 6f28ddd..524f69d 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -96,7 +96,7 @@
storeTask?: DelayedTask;
override disconnectedCallback() {
- this.storeTask?.cancel();
+ this.storeTask?.flush();
super.disconnectedCallback();
}
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
index 3176f61..65b1778 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.ts
@@ -167,6 +167,8 @@
<g id="new"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M23 12l-2.44-2.78.34-3.68-3.61-.82-1.89-3.18L12 3 8.6 1.54 6.71 4.72l-3.61.81.34 3.68L1 12l2.44 2.78-.34 3.69 3.61.82 1.89 3.18L12 21l3.4 1.46 1.89-3.18 3.61-.82-.34-3.68L23 12zm-4.51 2.11l.26 2.79-2.74.62-1.43 2.41L12 18.82l-2.58 1.11-1.43-2.41-2.74-.62.26-2.8L3.66 12l1.85-2.12-.26-2.78 2.74-.61 1.43-2.41L12 5.18l2.58-1.11 1.43 2.41 2.74.62-.26 2.79L20.34 12l-1.85 2.11zM11 15h2v2h-2zm0-8h2v6h-2z"/></g>
<!-- This SVG is a copy from material.io https://fonts.google.com/icons?selected=Material+Icons:arrow_right_alt -->
<g id="arrow-right"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M14 6l-1.41 1.41L16.17 11H4v2h12.17l-3.58 3.59L14 18l6-6z"/></g>
+ <!-- This SVG is a copy from material.io https://fonts.google.com/icons?selected=Material+Icons:cancel -->
+ <g id="cancel"><path xmlns="http://www.w3.org/2000/svg" d="M0 0h24v24H0z" fill="none"/><path xmlns="http://www.w3.org/2000/svg" d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/></g>
</defs>
</svg>
</iron-iconset-svg>`;
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
index 5317832..0dff8c3 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
@@ -71,10 +71,10 @@
// TODO(TS): to avoid ts error for:
// Only public and protected methods of the base class are accessible
// via the 'super' keyword.
- // we call IronFocsablesHelper directly here
- // Currently IronFocsablesHelper is not exported from iron-focusables-helper
- // as it should so we use Polymer.IronFocsablesHelper here instead
- // (can not use the IronFocsablesHelperClass
+ // we call IronFocusablesHelper directly here
+ // Currently IronFocusablesHelper is not exported from iron-focusables-helper
+ // as it should so we use Polymer.IronFocusablesHelper here instead
+ // (can not use the IronFocusablesHelperClass
// in case different behavior due to singleton)
// once the type contains the exported member,
// should replace with:
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts b/polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
index 17b3bf7..55e7453 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
@@ -26,7 +26,8 @@
*/
@property({type: Boolean}) saveOnChange = false;
- @property({type: Boolean}) showTooltipBelow = false;
+ @property({type: Boolean, attribute: 'show-tooltip-below'})
+ showTooltipBelow = false;
// visible for testing
@state() mode: DiffViewMode = DiffViewMode.SIDE_BY_SIDE;
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
deleted file mode 100644
index 7596804..0000000
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {property} from '@polymer/decorators';
-import {check, Constructor} from '../../utils/common-util';
-import {
- Shortcut,
- ShortcutSection,
- SPECIAL_SHORTCUT,
-} from '../../services/shortcuts/shortcuts-config';
-import {
- SectionView,
- ShortcutListener,
- shortcutsServiceToken,
-} from '../../services/shortcuts/shortcuts-service';
-import {DIPolymerElement, resolve} from '../../models/dependency';
-
-export {
- Shortcut,
- ShortcutSection,
- SPECIAL_SHORTCUT,
- ShortcutListener,
- SectionView,
-};
-
-export const KeyboardShortcutMixin = <T extends Constructor<DIPolymerElement>>(
- superClass: T
-) => {
- /**
- * @polymer
- * @mixinClass
- */
- class Mixin extends superClass {
- // This enables `Shortcut` to be used in the html template.
- Shortcut = Shortcut;
-
- // This enables `ShortcutSection` to be used in the html template.
- ShortcutSection = ShortcutSection;
-
- private readonly getShortcutsService = resolve(this, shortcutsServiceToken);
-
- /** Used to disable shortcuts when the element is not visible. */
- private observer?: IntersectionObserver;
-
- /**
- * Enabling shortcuts only when the element is visible (see `observer`
- * above) is a great feature, but often what you want is for the *page* to
- * be visible, not the specific child element that registers keyboard
- * shortcuts. An example is the FileList in the ChangeView. So we allow
- * a broader observer target to be specified here, and fall back to
- * `this` as the default.
- */
- @property({type: Object})
- observerTarget: Element = this;
-
- /** Are shortcuts currently enabled? True only when element is visible. */
- private bindingsEnabled = false;
-
- override connectedCallback() {
- super.connectedCallback();
- this.createVisibilityObserver();
- this.enableBindings();
- }
-
- override disconnectedCallback() {
- this.destroyVisibilityObserver();
- this.disableBindings();
- super.disconnectedCallback();
- }
-
- /**
- * Creates an intersection observer that enables bindings when the
- * element is visible and disables them when the element is hidden.
- */
- private createVisibilityObserver() {
- if (!this.hasKeyboardShortcuts()) return;
- if (this.observer) return;
- this.observer = new IntersectionObserver(entries => {
- check(entries.length === 1, 'Expected one observer entry.');
- const isVisible = entries[0].isIntersecting;
- if (isVisible) {
- this.enableBindings();
- } else {
- this.disableBindings();
- }
- });
- this.observer.observe(this.observerTarget);
- }
-
- private destroyVisibilityObserver() {
- if (this.observer) this.observer.unobserve(this.observerTarget);
- }
-
- /**
- * Enables all the shortcuts returned by keyboardShortcuts().
- * This is a private method being called when the element becomes
- * connected or visible.
- */
- private enableBindings() {
- if (!this.hasKeyboardShortcuts()) return;
- if (this.bindingsEnabled) return;
- this.bindingsEnabled = true;
-
- this.getShortcutsService().attachHost(this, this.keyboardShortcuts());
- }
-
- /**
- * Disables all the shortcuts returned by keyboardShortcuts().
- * This is a private method being called when the element becomes
- * disconnected or invisible.
- */
- private disableBindings() {
- if (!this.bindingsEnabled) return;
- this.bindingsEnabled = false;
- this.getShortcutsService().detachHost(this);
- }
-
- private hasKeyboardShortcuts() {
- return this.keyboardShortcuts().length > 0;
- }
-
- keyboardShortcuts(): ShortcutListener[] {
- return [];
- }
- }
-
- return Mixin as T & Constructor<KeyboardShortcutMixinInterface>;
-};
-
-/** The interface corresponding to KeyboardShortcutMixin */
-export interface KeyboardShortcutMixinInterface {
- keyboardShortcuts(): ShortcutListener[];
-}
diff --git a/polygerrit-ui/app/models/config/config-model.ts b/polygerrit-ui/app/models/config/config-model.ts
index dd26182..6861348 100644
--- a/polygerrit-ui/app/models/config/config-model.ts
+++ b/polygerrit-ui/app/models/config/config-model.ts
@@ -12,6 +12,7 @@
import {select} from '../../utils/observable-util';
import {Model} from '../model';
import {define} from '../dependency';
+import {getDocsBaseUrl} from '../../utils/url-util';
export interface ConfigState {
repoConfig?: ConfigInfo;
@@ -35,6 +36,20 @@
configState => configState.serverConfig
);
+ public mergeabilityComputationBehavior$ = select(
+ this.serverConfig$,
+ serverConfig => serverConfig?.change?.mergeability_computation_behavior
+ );
+
+ public docsBaseUrl$ = select(
+ this.serverConfig$.pipe(
+ switchMap(serverConfig =>
+ from(getDocsBaseUrl(serverConfig, this.restApiService))
+ )
+ ),
+ url => url
+ );
+
private subscriptions: Subscription[];
constructor(
@@ -59,11 +74,12 @@
];
}
- updateRepoConfig(repoConfig?: ConfigInfo) {
+ private updateRepoConfig(repoConfig?: ConfigInfo) {
const current = this.subject$.getValue();
this.subject$.next({...current, repoConfig});
}
+ // visible for testing
updateServerConfig(serverConfig?: ServerInfo) {
const current = this.subject$.getValue();
this.subject$.next({...current, serverConfig});
diff --git a/polygerrit-ui/app/models/dependency.ts b/polygerrit-ui/app/models/dependency.ts
index ae9cc64..5499db2 100644
--- a/polygerrit-ui/app/models/dependency.ts
+++ b/polygerrit-ui/app/models/dependency.ts
@@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {ReactiveController, ReactiveControllerHost} from 'lit';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
/**
* This module provides the ability to do dependency injection in components.
@@ -180,49 +179,6 @@
}
/**
- * Because Polymer doesn't (yet) depend on ReactiveControllerHost, this adds a
- * work-around base-class to make this work for Polymer.
- */
-export class DIPolymerElement
- extends PolymerElement
- implements ReactiveControllerHost
-{
- private readonly ___controllers: ReactiveController[] = [];
-
- override connectedCallback() {
- for (const c of this.___controllers) {
- c.hostConnected?.();
- }
- super.connectedCallback();
- }
-
- override disconnectedCallback() {
- super.disconnectedCallback();
- for (const c of this.___controllers) {
- c.hostDisconnected?.();
- }
- }
-
- addController(controller: ReactiveController) {
- this.___controllers.push(controller);
-
- if (this.isConnected) controller.hostConnected?.();
- }
-
- removeController(controller: ReactiveController) {
- const idx = this.___controllers.indexOf(controller);
- if (idx < 0) return;
- this.___controllers?.splice(idx, 1);
- }
-
- requestUpdate() {}
-
- get updateComplete(): Promise<boolean> {
- return Promise.resolve(true);
- }
-}
-
-/**
* A callback for a value.
*/
type Callback<T> = (value: T) => void;
diff --git a/polygerrit-ui/app/models/dependency_test.ts b/polygerrit-ui/app/models/dependency_test.ts
index 76d5dfd..49e8580 100644
--- a/polygerrit-ui/app/models/dependency_test.ts
+++ b/polygerrit-ui/app/models/dependency_test.ts
@@ -3,10 +3,8 @@
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {define, provide, resolve, DIPolymerElement} from './dependency';
+import {define, provide, resolve} from './dependency';
import {html, LitElement} from 'lit';
-import {customElement as polyCustomElement} from '@polymer/decorators';
-import {html as polyHtml} from '@polymer/polymer/lib/utils/html-tag';
import {customElement, property, query} from 'lit/decorators';
import '../test/common-test-setup-karma.js';
@@ -55,33 +53,11 @@
}
}
-@polyCustomElement('polymer-foo-provider')
-export class PolymerFooProviderElement extends DIPolymerElement {
- bar() {
- return this.$.bar as BarProviderElement;
- }
-
- override connectedCallback() {
- provide(this, fooToken, () => new FooImpl('foo'));
- super.connectedCallback();
- }
-
- static get template() {
- return polyHtml`<bar-provider id="bar"></bar-provider>`;
- }
-}
-
@customElement('bar-provider')
export class BarProviderElement extends LitElement {
@query('leaf-lit-element')
litChild?: LeafLitElement;
- @query('leaf-polymer-element')
- polymerChild?: LeafPolymerElement;
-
- @property({type: Boolean})
- public showLit = true;
-
override connectedCallback() {
super.connectedCallback();
provide(this, barToken, () => this.create());
@@ -94,11 +70,7 @@
}
override render() {
- if (this.showLit) {
- return html`<leaf-lit-element></leaf-lit-element>`;
- } else {
- return html`<leaf-polymer-element></leaf-polymer-element>`;
- }
+ return html`<leaf-lit-element></leaf-lit-element>`;
}
}
@@ -116,20 +88,6 @@
}
}
-@polyCustomElement('leaf-polymer-element')
-export class LeafPolymerElement extends DIPolymerElement {
- readonly barRef = resolve(this, barToken);
-
- override connectedCallback() {
- super.connectedCallback();
- assert.isDefined(this.barRef());
- }
-
- static get template() {
- return polyHtml`Hello`;
- }
-}
-
suite('Dependency', () => {
test('It instantiates', async () => {
const fixture = fixtureFromElement('lit-foo-provider');
@@ -138,13 +96,6 @@
assert.isDefined(element.bar?.litChild?.barRef());
});
- test('It instantiates in polymer', async () => {
- const fixture = fixtureFromElement('polymer-foo-provider');
- const element = fixture.instantiate();
- await element.bar().updateComplete;
- assert.isDefined(element.bar().litChild?.barRef());
- });
-
test('It works by connecting and reconnecting', async () => {
const fixture = fixtureFromElement('lit-foo-provider');
const element = fixture.instantiate();
@@ -159,29 +110,12 @@
await element.updateComplete;
assert.isDefined(element.bar?.litChild?.barRef());
});
-
- test('It works by connecting and reconnecting Polymer', async () => {
- const fixture = fixtureFromElement('lit-foo-provider');
- const element = fixture.instantiate();
- await element.updateComplete;
-
- const beta = element.bar;
- assert.isDefined(beta);
- assert.isNotNull(beta);
- assert.isDefined(element.bar?.litChild?.barRef());
-
- beta!.showLit = false;
- await element.updateComplete;
- assert.isDefined(element.bar?.polymerChild?.barRef());
- });
});
declare global {
interface HTMLElementTagNameMap {
'lit-foo-provider': LitFooProviderElement;
- 'polymer-foo-provider': PolymerFooProviderElement;
'bar-provider': BarProviderElement;
'leaf-lit-element': LeafLitElement;
- 'leaf-polymer-element': LeafPolymerElement;
}
}
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
index c27da15..7ca429b 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
@@ -26,6 +26,8 @@
import {FlagsService} from '../flags/flags';
import {define} from '../../models/dependency';
+export {Shortcut, ShortcutSection};
+
export type SectionView = Array<{binding: string[][]; text: string}>;
export interface ShortcutListener {
@@ -67,12 +69,6 @@
*/
private readonly activeShortcuts = new Set<Shortcut>();
- /**
- * Keeps track of cleanup callbacks (which remove keyboard listeners) that
- * have to be invoked when a component unregisters itself.
- */
- private readonly cleanupsPerHost = new Map<HTMLElement, (() => void)[]>();
-
/** Static map built in the constructor by iterating over the config. */
private readonly bindings = new Map<Shortcut, Binding[]>();
@@ -239,23 +235,6 @@
};
}
- /**
- * Being called by the Polymer specific KeyboardShortcutMixin.
- */
- attachHost(host: HTMLElement, shortcuts: ShortcutListener[]) {
- const cleanups: (() => void)[] = [];
- for (const s of shortcuts) {
- cleanups.push(this.addShortcutListener(s.shortcut, s.listener));
- }
- this.cleanupsPerHost.set(host, cleanups);
- }
-
- detachHost(host: HTMLElement) {
- const cleanups = this.cleanupsPerHost.get(host);
- for (const cleanup of cleanups ?? []) cleanup();
- return true;
- }
-
addListener(listener: ShortcutViewListener) {
this.listeners.add(listener);
listener(this.directoryView());
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
index 9e978a7..69c371e 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
@@ -120,9 +120,7 @@
test('active shortcuts by section', () => {
assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {});
- service.attachHost(document.createElement('div'), [
- {shortcut: Shortcut.NEXT_FILE, listener: _ => {}},
- ]);
+ service.addShortcutListener(Shortcut.NEXT_FILE, _ => {});
assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
[ShortcutSection.NAVIGATION]: [
{
@@ -132,10 +130,7 @@
},
],
});
-
- service.attachHost(document.createElement('div'), [
- {shortcut: Shortcut.NEXT_LINE, listener: _ => {}},
- ]);
+ service.addShortcutListener(Shortcut.NEXT_LINE, _ => {});
assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
[ShortcutSection.DIFFS]: [
{
@@ -156,10 +151,8 @@
],
});
- service.attachHost(document.createElement('div'), [
- {shortcut: Shortcut.SEARCH, listener: _ => {}},
- {shortcut: Shortcut.GO_TO_OPENED_CHANGES, listener: _ => {}},
- ]);
+ service.addShortcutListener(Shortcut.SEARCH, _ => {});
+ service.addShortcutListener(Shortcut.GO_TO_OPENED_CHANGES, _ => {});
assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
[ShortcutSection.DIFFS]: [
{
@@ -196,13 +189,11 @@
test('directory view', () => {
assert.deepEqual(mapToObject(service.directoryView()), {});
- service.attachHost(document.createElement('div'), [
- {shortcut: Shortcut.GO_TO_OPENED_CHANGES, listener: _ => {}},
- {shortcut: Shortcut.NEXT_FILE, listener: _ => {}},
- {shortcut: Shortcut.NEXT_LINE, listener: _ => {}},
- {shortcut: Shortcut.SAVE_COMMENT, listener: _ => {}},
- {shortcut: Shortcut.SEARCH, listener: _ => {}},
- ]);
+ service.addShortcutListener(Shortcut.GO_TO_OPENED_CHANGES, _ => {});
+ service.addShortcutListener(Shortcut.NEXT_FILE, _ => {});
+ service.addShortcutListener(Shortcut.NEXT_LINE, _ => {});
+ service.addShortcutListener(Shortcut.SAVE_COMMENT, _ => {});
+ service.addShortcutListener(Shortcut.SEARCH, _ => {});
assert.deepEqual(mapToObject(service.directoryView()), {
[ShortcutSection.DIFFS]: [
{binding: [['j'], ['↓']], text: 'Go to next line'},
diff --git a/polygerrit-ui/app/styles/gr-submit-requirements-styles.ts b/polygerrit-ui/app/styles/gr-submit-requirements-styles.ts
index e488f71..a666bd0 100644
--- a/polygerrit-ui/app/styles/gr-submit-requirements-styles.ts
+++ b/polygerrit-ui/app/styles/gr-submit-requirements-styles.ts
@@ -14,4 +14,7 @@
iron-icon.error {
color: var(--deemphasized-text-color);
}
+ iron-icon.cancel {
+ color: var(--error-foreground);
+ }
`;
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 19dd8d7..54efa5c 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -238,6 +238,12 @@
}
return labels.filter(unique);
}
+export function iconForRequirement(requirement: SubmitRequirementResultInfo) {
+ if (isBlockingCondition(requirement)) {
+ return 'cancel';
+ }
+ return iconForStatus(requirement.status);
+}
export function iconForStatus(status: SubmitRequirementStatus) {
switch (status) {
@@ -430,3 +436,13 @@
label => !onlyInNotApplicableLabels.includes(label)
);
}
+
+export function isBlockingCondition(
+ requirement: SubmitRequirementResultInfo
+): boolean {
+ if (requirement.status !== SubmitRequirementStatus.UNSATISFIED) return false;
+
+ return !!requirement.submittability_expression_result.passing_atoms?.some(
+ atom => atom.match(/^label[0-9]*:[\w-]+=MIN$/)
+ );
+}
diff --git a/polygerrit-ui/app/utils/label-util_test.ts b/polygerrit-ui/app/utils/label-util_test.ts
index 3f3aa1a..faf7e00 100644
--- a/polygerrit-ui/app/utils/label-util_test.ts
+++ b/polygerrit-ui/app/utils/label-util_test.ts
@@ -22,6 +22,7 @@
computeOrderedLabelValues,
mergeLabelInfoMaps,
getApplicableLabels,
+ isBlockingCondition,
} from './label-util';
import {
AccountId,
@@ -44,6 +45,7 @@
SubmitRequirementResultInfo,
SubmitRequirementStatus,
LabelNameToInfoMap,
+ SubmitRequirementExpressionInfoStatus,
} from '../api/rest-api';
const VALUES_0 = {
@@ -762,4 +764,47 @@
assert.deepEqual(getApplicableLabels(change), [label]);
});
});
+
+ suite('isBlockingCondition', () => {
+ test('true', () => {
+ const requirement: SubmitRequirementResultInfo = {
+ name: 'Code-Review',
+ description:
+ "At least one maximum vote for label 'Code-Review' is required",
+ status: SubmitRequirementStatus.UNSATISFIED,
+ is_legacy: false,
+ submittability_expression_result: {
+ expression:
+ 'label:Code-Review=MAX,user=non_uploader AND -label:Code-Review=MIN',
+ fulfilled: false,
+ status: SubmitRequirementExpressionInfoStatus.FAIL,
+ passing_atoms: ['label:Code-Review=MIN'],
+ failing_atoms: ['label:Code-Review=MAX,user=non_uploader'],
+ },
+ };
+ assert.isTrue(isBlockingCondition(requirement));
+ });
+
+ test('false', () => {
+ const requirement: SubmitRequirementResultInfo = {
+ name: 'Code-Review',
+ description:
+ "At least one maximum vote for label 'Code-Review' is required",
+ status: SubmitRequirementStatus.UNSATISFIED,
+ is_legacy: false,
+ submittability_expression_result: {
+ expression:
+ 'label:Code-Review=MAX,user=non_uploader AND -label:Code-Review=MIN',
+ fulfilled: false,
+ status: SubmitRequirementExpressionInfoStatus.FAIL,
+ passing_atoms: [],
+ failing_atoms: [
+ 'label:Code-Review=MAX,user=non_uploader',
+ 'label:Code-Review=MIN',
+ ],
+ },
+ };
+ assert.isFalse(isBlockingCondition(requirement));
+ });
+ });
});