Separate file list header into new component
Change-Id: I45619dfaaf005a89f8de6d3e9dc39a2bcbca1893
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index d6ad5ae..39ae763 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -21,12 +21,10 @@
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../diff/gr-diff-preferences/gr-diff-preferences.html">
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
-<link rel="import" href="../../shared/gr-select/gr-select.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-editable-content/gr-editable-content.html">
-<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
@@ -37,6 +35,7 @@
<link rel="import" href="../gr-commit-info/gr-commit-info.html">
<link rel="import" href="../gr-download-dialog/gr-download-dialog.html">
<link rel="import" href="../gr-file-list/gr-file-list.html">
+<link rel="import" href="../gr-file-list-header/gr-file-list-header.html">
<link rel="import" href="../gr-messages-list/gr-messages-list.html">
<link rel="import" href="../gr-related-changes-list/gr-related-changes-list.html">
<link rel="import" href="../gr-reply-dialog/gr-reply-dialog.html">
@@ -80,9 +79,6 @@
font-size: 1.2em;
font-weight: bold;
}
- .prefsButton {
- float: right;
- }
gr-change-star {
margin-right: .25em;
vertical-align: -.425em;
@@ -152,45 +148,10 @@
flex: 1;
overflow-x: hidden;
}
- .collapseToggleButton {
- text-decoration: none;
- }
.relatedChanges {
flex: 1 1 auto;
overflow: hidden;
}
- .patchInfo {
- border: 1px solid #ddd;
- margin: 1em var(--default-horizontal-margin);
- }
- .patchInfoEdit .patchInfo-header {
- background-color: #fcfad6;
- }
- .patchInfoOldPatchSet .patchInfo-header {
- background-color: #fff9c4;
- }
- .patchInfoOldPatchSet .latestPatchContainer {
- display: initial;
- }
- .patchInfo-header,
- .fileList {
- padding: .5em calc(var(--default-horizontal-margin) / 2);
- }
- .patchInfo-header {
- background-color: #f6f6f6;
- border-bottom: 1px solid #ebebeb;
- display: flex;
- justify-content: space-between;
- }
- .latestPatchContainer {
- display: none;
- }
- .patchSetSelect {
- max-width: 8em;
- }
- gr-editable-label.descriptionLabel {
- max-width: 100%;
- }
.mobile {
display: none;
}
@@ -203,13 +164,6 @@
height: 0;
margin-bottom: 1em;
}
- #diffPrefsContainer,
- .rightControls {
- margin: auto 0 auto auto;
- }
- .patchInfo-header-wrapper {
- width: 100%;
- }
#commitMessage.collapsed {
max-height: 36em;
overflow: hidden;
@@ -246,32 +200,12 @@
.showOnEdit {
display: none;
}
- .editLoaded .hideOnEdit {
- display: none;
+ .patchInfo {
+ border: 1px solid #ddd;
+ margin: 1em var(--default-horizontal-margin);
}
- .editLoaded .showOnEdit {
- display: initial;
- }
- .fileList-header {
- display: flex;
- font-weight: bold;
- justify-content: space-between;
- margin-bottom: .5em;
- }
- .rightControls {
- display: flex;
- flex-wrap: wrap;
- font-weight: normal;
- justify-content: flex-end;
- }
- .separator {
- margin: 0 .25em;
- }
- .expandInline {
- padding-right: .25em;
- }
- .patchSetSelect {
- max-width: 8em;
+ #fileList {
+ padding: .5em calc(var(--default-horizontal-margin) / 2);
}
@media screen and (min-width: 80em) {
.commitMessage {
@@ -357,7 +291,7 @@
<div class="container loading" hidden$="[[!_loading]]">Loading...</div>
<div
id="mainContent"
- class$="container [[_computeEditLoadedClass(_editLoaded)]]"
+ class="container"
hidden$="{{_loading}}">
<div class$="hideOnMobileOverlay [[_computeHeaderClass(_change)]]">
<span class="header-title">
@@ -486,138 +420,44 @@
</div>
</div>
</section>
- <section class$="patchInfo hideOnMobileOverlay [[_computePatchInfoClass(_patchRange.patchNum,
- _allPatchSets)]]">
- <div class="patchInfo-header">
- <div class="patchInfo-header-wrapper">
- <label class="patchSelectLabel" for="patchSetSelect">
- Patch set
- </label>
- <gr-select
- id="patchSetSelect"
- bind-value="{{_selectedPatchSet}}"
- class="patchSetSelect"
- on-change="_handlePatchChange">
- <select>
- <template is="dom-repeat" items="[[_allPatchSets]]"
- as="patchNum">
- <option
- value$="[[patchNum.num]]"
- disabled$="[[_computePatchSetDisabled(patchNum.num, _patchRange.basePatchNum, _sortedRevisions)]]">
- [[patchNum.num]]
- /
- [[computeLatestPatchNum(_allPatchSets)]]
- [[_computePatchSetCommentsString(_comments, patchNum.num)]]
- [[_computePatchSetDescription(_change, patchNum.num)]]
- </option>
- </template>
- </select>
- </gr-select>
- /
- <gr-commit-info
- change="[[_change]]"
- server-config="[[_serverConfig]]"
- commit-info="[[_commitInfo]]"></gr-commit-info>
- <span class="latestPatchContainer">
- /
- <a href$="[[_computeChangeUrl(_change)]]">Go to latest patch set</a>
- </span>
- <span class="downloadContainer desktop">
- /
- <gr-button link
- class="download"
- on-tap="_handleDownloadTap">Download</gr-button>
- </span>
- <span class="descriptionContainer hideOnEdit">
- /
- <gr-editable-label
- id="descriptionLabel"
- class="descriptionLabel"
- value="[[_computePatchSetDescription(_change, _selectedPatchSet)]]"
- placeholder="[[_computeDescriptionPlaceholder(_descriptionReadOnly)]]"
- read-only="[[_descriptionReadOnly]]"
- on-changed="_handleDescriptionChanged"></gr-editable-label>
- </span>
- <span id="diffPrefsContainer"
- class="hideOnEdit"
- hidden$="[[_computePrefsButtonHidden(_diffPrefs, _loggedIn)]]"
- hidden>
- <gr-button link
- class="prefsButton desktop"
- on-tap="_handlePrefsTap">Diff Preferences</gr-button>
- </span>
- </div>
- </div>
- <div class="fileList">
- <div class="fileList-header">
- <div>Files</div>
- <div class="rightControls">
- <template is="dom-if"
- if="[[_fileListActionsVisible(_shownFileCount, _maxFilesForBulkActions)]]">
- <gr-button
- id="expandBtn"
- link
- on-tap="_expandAllDiffs">Show diffs</gr-button>
- <span class="separator">/</span>
- <gr-button
- id="collapseBtn"
- link
- on-tap="_collapseAllDiffs">Hide diffs</gr-button>
- </template>
- <template is="dom-if"
- if="[[!_fileListActionsVisible(_shownFileCount, _maxFilesForBulkActions)]]">
- <div class="warning">
- Bulk actions disabled because there are too many files.
- </div>
- </template>
- <span class="separator">/</span>
- <gr-select
- id="modeSelect"
- bind-value="{{viewState.diffMode}}">
- <select>
- <option value="SIDE_BY_SIDE">Side By Side</option>
- <option value="UNIFIED_DIFF">Unified</option>
- </select>
- </gr-select>
- <span class="separator">/</span>
- <label>
- Diff against
- <gr-select id="patchChange" bind-value="{{_diffAgainst}}"
- class="patchSetSelect" on-change="_handleBasePatchChange">
- <select>
- <option value="PARENT">Base</option>
- <template
- is="dom-repeat"
- items="[[_allPatchSets]]"
- as="patchNum">
- <option
- disabled$="[[_computeBasePatchDisabled(patchNum.num, _patchRange.patchNum, _sortedRevisions)]]"
- value$="[[patchNum.num]]">
- [[patchNum.num]]
- [[patchNum.desc]]
- </option>
- </template>
- </select>
- </gr-select>
- </label>
- </div>
- </div>
- <gr-file-list id="fileList"
- diff-prefs="{{_diffPrefs}}"
- change="[[_change]]"
- change-num="[[_changeNum]]"
- patch-range="{{_patchRange}}"
- comments="[[_comments]]"
- drafts="[[_diffDrafts]]"
- revisions="[[_sortedRevisions]]"
- project-config="[[_projectConfig]]"
- selected-index="{{viewState.selectedFileIndex}}"
- diff-view-mode="[[viewState.diffMode]]"
- edit-loaded="[[_editLoaded]]"
- num-files-shown="{{_numFilesShown}}"
- file-list-increment="{{_numFilesShown}}"
- on-files-shown-changed="_setShownFiles"></gr-file-list>
- </div>
+ <section class="patchInfo hideOnMobileOverlay">
+ <gr-file-list-header
+ id="fileListHeader"
+ account="[[_account]]"
+ all-patch-sets="[[_allPatchSets]]"
+ change="[[_change]]"
+ change-num="[[_changeNum]]"
+ comments="[[_comments]]"
+ commit-info="[[_commitInfo]]"
+ change-url="[[_computeChangeUrl(_change)]]"
+ edit-loaded="[[_editLoaded]]"
+ logged-in="[[_loggedIn]]"
+ server-config="[[_serverConfig]]"
+ shown-file-count="[[_shownFileCount]]"
+ diff-prefs="[[_diffPrefs]]"
+ diff-view-mode="{{viewState.diffMode}}"
+ patch-range="{{_patchRange}}"
+ revisions="[[_sortedRevisions]]"
+ on-open-diff-prefs="_handleOpenDiffPrefs"
+ on-open-download-dialog="_handleOpenDownloadDialog"
+ on-expand-diffs="_expandAllDiffs"
+ on-collapse-diffs="_collapseAllDiffs">
+ </gr-file-list-header>
+ <gr-file-list id="fileList"
+ diff-prefs="{{_diffPrefs}}"
+ change="[[_change]]"
+ change-num="[[_changeNum]]"
+ patch-range="{{_patchRange}}"
+ comments="[[_comments]]"
+ drafts="[[_diffDrafts]]"
+ revisions="[[_sortedRevisions]]"
+ project-config="[[_projectConfig]]"
+ selected-index="{{viewState.selectedFileIndex}}"
+ diff-view-mode="[[viewState.diffMode]]"
+ edit-loaded="[[_editLoaded]]"
+ num-files-shown="{{_numFilesShown}}"
+ file-list-increment="{{_numFilesShown}}"
+ on-files-shown-changed="_setShownFiles"></gr-file-list>
</section>
<gr-messages-list id="messageList"
class="hideOnMobileOverlay"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index b60453b..1991c78 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -23,8 +23,6 @@
const MIN_LINES_FOR_COMMIT_COLLAPSE = 30;
const DEFAULT_NUM_FILES_SHOWN = 200;
- // Maximum length for patch set descriptions.
- const PATCH_DESC_MAX_LENGTH = 500;
const REVIEWERS_REGEX = /^(R|CC)=/gm;
const MIN_CHECK_INTERVAL_SECS = 0;
@@ -135,17 +133,9 @@
computed:
'_computeChangeIdCommitMessageError(_latestCommitMessage, _change)',
},
- // Caps the number of files that can be shown and have the 'show diffs' /
- // 'hide diffs' buttons still be functional.
- _maxFilesForBulkActions: {
- type: Number,
- readOnly: true,
- value: 225,
- },
/** @type {?} */
_patchRange: {
type: Object,
- observer: '_updateSelected',
},
_relatedChangesLoading: {
type: Boolean,
@@ -175,10 +165,6 @@
type: Boolean,
value: false,
},
- _descriptionReadOnly: {
- type: Boolean,
- computed: '_computeDescriptionReadOnly(_loggedIn, _change, _account)',
- },
_replyDisabled: {
type: Boolean,
value: true,
@@ -293,10 +279,6 @@
this._sortedRevisions = this.sortRevisions(Object.values(revisions));
},
- _computePrefsButtonHidden(prefs, loggedIn) {
- return !loggedIn || !prefs;
- },
-
_handleEditCommitMessage(e) {
this._editingCommitMessage = true;
this.$.commitMessageEditor.focusTextarea();
@@ -338,11 +320,6 @@
return false;
},
- _handlePrefsTap(e) {
- e.preventDefault();
- this.$.fileList.openDiffPrefs();
- },
-
_handleCommentSave(e) {
if (!e.target.comment.__draft) { return; }
@@ -409,21 +386,16 @@
this._diffDrafts = diffDrafts;
},
- _handleBasePatchChange(e) {
- this._changePatchNum(this._selectedPatchSet, e.target.value, true);
- },
-
- _handlePatchChange(e) {
- this._changePatchNum(e.target.value, this._diffAgainst, true);
- },
-
_handleReplyTap(e) {
e.preventDefault();
this._openReplyDialog();
},
- _handleDownloadTap(e) {
- e.preventDefault();
+ _handleOpenDiffPrefs() {
+ this.$.fileList.openDiffPrefs();
+ },
+
+ _handleOpenDownloadDialog() {
this.$.downloadOverlay.open().then(() => {
this.$.downloadOverlay
.setFocusStops(this.$.downloadDialog.getFocusStops());
@@ -494,10 +466,6 @@
this._shownFileCount = e.detail.length;
},
- _fileListActionsVisible(shownFileCount, maxFilesForBulkActions) {
- return shownFileCount <= maxFilesForBulkActions;
- },
-
_expandAllDiffs() {
this.$.fileList.expandAllDiffs();
},
@@ -666,39 +634,12 @@
this._patchRange.patchNum ||
this.computeLatestPatchNum(this._allPatchSets));
- this._updateSelected();
+ this.$.fileListHeader.updateSelected();
const title = change.subject + ' (' + change.change_id.substr(0, 9) + ')';
this.fire('title-change', {title});
},
- /**
- * Change active patch to the provided patch num.
- * @param {number|string} basePatchNum the base patch to be viewed.
- * @param {number|string} patchNum the patch number to be viewed.
- * @param {boolean} opt_forceParams When set to true, the resulting URL will
- * always include the patch range, even if the requested patchNum is
- * known to be the latest.
- */
- _changePatchNum(patchNum, basePatchNum, opt_forceParams) {
- if (!opt_forceParams) {
- let currentPatchNum;
- if (this._change.current_revision) {
- currentPatchNum =
- this._change.revisions[this._change.current_revision]._number;
- } else {
- currentPatchNum = this.computeLatestPatchNum(this._allPatchSets);
- }
- if (this.patchNumEquals(patchNum, currentPatchNum) &&
- basePatchNum === 'PARENT') {
- Gerrit.Nav.navigateToChange(this._change);
- return;
- }
- }
- Gerrit.Nav.navigateToChange(this._change, patchNum,
- basePatchNum);
- },
-
_computeChangeUrl(change) {
return Gerrit.Nav.getUrlForChange(change);
},
@@ -753,37 +694,6 @@
return CHANGE_ID_ERROR.MISSING;
},
- _computePatchInfoClass(patchNum, allPatchSets) {
- if (this.patchNumEquals(patchNum, this.EDIT_NAME)) {
- return 'patchInfoEdit';
- }
-
- const latestNum = this.computeLatestPatchNum(allPatchSets);
- if (this.patchNumEquals(patchNum, latestNum)) {
- return '';
- }
- return 'patchInfoOldPatchSet';
- },
-
- /**
- * Determines if a patch number should be disabled based on value of the
- * basePatchNum from gr-file-list.
- * @param {number} patchNum Patch number available in dropdown
- * @param {number|string} basePatchNum Base patch number from file list
- * @return {boolean}
- */
- _computePatchSetDisabled(patchNum, basePatchNum) {
- if (basePatchNum === 'PARENT') { return false; }
-
- return this.findSortedIndex(patchNum, this._sortedRevisions) <=
- this.findSortedIndex(basePatchNum, this._sortedRevisions);
- },
-
- _computeBasePatchDisabled(patchNum, currentPatchNum) {
- return this.findSortedIndex(patchNum, this._sortedRevisions) >=
- this.findSortedIndex(currentPatchNum, this._sortedRevisions);
- },
-
_computeLabelNames(labels) {
return Object.keys(labels).sort();
},
@@ -1163,81 +1073,11 @@
]);
},
- _updateSelected() {
- this._selectedPatchSet = this._patchRange.patchNum;
- this._diffAgainst = this._patchRange.basePatchNum;
- },
-
- _computePatchSetDescription(change, patchNum) {
- const rev = this.getRevisionByPatchNum(change.revisions, patchNum);
- return (rev && rev.description) ?
- rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
- },
-
- _computePatchSetCommentsString(allComments, patchNum) {
- let numComments = 0;
- let numUnresolved = 0;
- for (const file in allComments) {
- if (allComments.hasOwnProperty(file)) {
- numComments += this.$.fileList.getCommentsForPath(
- allComments, patchNum, file).length;
- numUnresolved += this.$.fileList.computeUnresolvedNum(
- allComments, {}, patchNum, file);
- }
- }
- let commentsStr = '';
- if (numComments > 0) {
- commentsStr = '(' + numComments + ' comments';
- if (numUnresolved > 0) {
- commentsStr += ', ' + numUnresolved + ' unresolved';
- }
- commentsStr += ')';
- }
- return commentsStr;
- },
-
- _computeDescriptionPlaceholder(readOnly) {
- return (readOnly ? 'No' : 'Add a') + ' patch set description';
- },
-
- _handleDescriptionChanged(e) {
- const desc = e.detail.trim();
- const rev = this.getRevisionByPatchNum(this._change.revisions,
- this._selectedPatchSet);
- const sha = this._getPatchsetHash(this._change.revisions, rev);
- this.$.restAPI.setDescription(this._changeNum,
- this._selectedPatchSet, desc)
- .then(res => {
- if (res.ok) {
- this.set(['_change', 'revisions', sha, 'description'], desc);
- }
- });
- },
-
-
- /**
- * @param {!Object} revisions The revisions object keyed by revision hashes
- * @param {?Object} patchSet A revision already fetched from {revisions}
- * @return {string|undefined} the SHA hash corresponding to the revision.
- */
- _getPatchsetHash(revisions, patchSet) {
- for (const rev in revisions) {
- if (revisions.hasOwnProperty(rev) &&
- revisions[rev] === patchSet) {
- return rev;
- }
- }
- },
-
_computeCanStartReview(loggedIn, change, account) {
return !!(loggedIn && change.work_in_progress &&
change.owner._account_id === account._account_id);
},
- _computeDescriptionReadOnly(loggedIn, change, account) {
- return !(loggedIn && (account._account_id === change.owner._account_id));
- },
-
_computeReplyDisabled() { return false; },
_computeChangePermalinkAriaLabel(changeNum) {
@@ -1424,9 +1264,5 @@
const patchRange = patchRangeRecord.base || {};
return this.patchNumEquals(patchRange.patchNum, this.EDIT_NAME);
},
-
- _computeEditLoadedClass(editLoaded) {
- return editLoaded ? 'editLoaded' : '';
- },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 5389f1c6..24181e5 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -171,6 +171,20 @@
assert.isFalse(element.$.mainContent.classList.contains('overlayOpen'));
});
+ test('expand all messages when expand-diffs fired', () => {
+ const handleExpand =
+ sandbox.stub(element.$.fileList, 'expandAllDiffs');
+ element.$.fileListHeader.fire('expand-diffs');
+ assert.isTrue(handleExpand.called);
+ });
+
+ test('collapse all messages when collapse-diffs fired', () => {
+ const handleCollapse =
+ sandbox.stub(element.$.fileList, 'collapseAllDiffs');
+ element.$.fileListHeader.fire('collapse-diffs');
+ assert.isTrue(handleCollapse.called);
+ });
+
test('X should expand all messages', () => {
const handleExpand =
sandbox.stub(element.$.messageList, 'handleExpandCollapse');
@@ -238,79 +252,13 @@
});
});
- test('Diff preferences hidden when no prefs or logged out', () => {
- element._loggedIn = false;
- flushAsynchronousOperations();
- assert.isTrue(element.$.diffPrefsContainer.hidden);
-
- element._loggedIn = true;
- flushAsynchronousOperations();
- assert.isTrue(element.$.diffPrefsContainer.hidden);
-
- element._loggedIn = false;
- element._diffPrefs = {font_size: '12'};
- flushAsynchronousOperations();
- assert.isTrue(element.$.diffPrefsContainer.hidden);
-
- element._loggedIn = true;
- flushAsynchronousOperations();
- assert.isFalse(element.$.diffPrefsContainer.hidden);
- });
-
- test('prefsButton opens gr-diff-preferences', () => {
- const handlePrefsTapSpy = sandbox.spy(element, '_handlePrefsTap');
+ test('diff preferences open when open-diff-prefs is fired', () => {
const overlayOpenStub = sandbox.stub(element.$.fileList,
'openDiffPrefs');
- const prefsButton = Polymer.dom(element.root).querySelectorAll(
- '.prefsButton')[0];
-
- MockInteractions.tap(prefsButton);
-
- assert.isTrue(handlePrefsTapSpy.called);
+ element.$.fileListHeader.fire('open-diff-prefs');
assert.isTrue(overlayOpenStub.called);
});
- test('_computeDescriptionReadOnly', () => {
- assert.equal(element._computeDescriptionReadOnly(false,
- {owner: {_account_id: 1}}, {_account_id: 1}), true);
- assert.equal(element._computeDescriptionReadOnly(true,
- {owner: {_account_id: 0}}, {_account_id: 1}), true);
- assert.equal(element._computeDescriptionReadOnly(true,
- {owner: {_account_id: 1}}, {_account_id: 1}), false);
- });
-
- test('_computeDescriptionPlaceholder', () => {
- assert.equal(element._computeDescriptionPlaceholder(true),
- 'No patch set description');
- assert.equal(element._computeDescriptionPlaceholder(false),
- 'Add a patch set description');
- });
-
- test('_computePatchSetDisabled', () => {
- element._sortedRevisions = [
- {_number: 1},
- {_number: 2},
- {_number: element.EDIT_NAME, basePatchNum: 2},
- {_number: 3},
- ];
- let basePatchNum = 'PARENT';
- let patchNum = 1;
- assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
- false);
- basePatchNum = 1;
- assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
- true);
- patchNum = 2;
- assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
- false);
- basePatchNum = element.EDIT_NAME;
- assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
- true);
- patchNum = '3';
- assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
- false);
- });
-
test('_prepareCommitMsgForLinkify', () => {
let commitMessage = 'R=test@google.com';
let result = element._prepareCommitMsgForLinkify(commitMessage);
@@ -325,80 +273,6 @@
assert.equal(result, 'CC=\u200Btest@google.com');
}),
- test('_computePatchSetCommentsString', () => {
- // Test string with unresolved comments.
- comments = {
- foo: 'foo comments',
- bar: 'bar comments',
- xyz: 'xyz comments',
- };
- sandbox.stub(element.$.fileList, 'getCommentsForPath', (c, p, f) => {
- if (f == 'foo') {
- return ['comment1', 'comment2'];
- } else if (f == 'bar') {
- return ['comment1'];
- } else {
- return [];
- }
- });
- sandbox.stub(element.$.fileList, 'computeUnresolvedNum', (c, d, p, f) => {
- if (f == 'foo') {
- return 0;
- } else if (f == 'bar') {
- return 1;
- } else {
- return 0;
- }
- });
- assert.equal(element._computePatchSetCommentsString(comments, 1),
- '(3 comments, 1 unresolved)');
-
- // Test string with no unresolved comments.
- delete comments['bar'];
- assert.equal(element._computePatchSetCommentsString(comments, 1),
- '(2 comments)');
-
- // Test string with no comments.
- delete comments['foo'];
- assert.equal(element._computePatchSetCommentsString(comments, 1), '');
- });
-
- test('_handleDescriptionChanged', () => {
- const putDescStub = sandbox.stub(element.$.restAPI, 'setDescription')
- .returns(Promise.resolve({ok: true}));
- sandbox.stub(element, '_computeDescriptionReadOnly');
-
- element._changeNum = '42';
- element._patchRange = {
- basePatchNum: 'PARENT',
- patchNum: 1,
- };
- element._selectedPatchNum = '1';
- element._change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- revisions: {
- rev1: {_number: 1, description: 'test', commit: {commit: 'rev1'}},
- },
- current_revision: 'rev1',
- status: 'NEW',
- labels: {},
- actions: {},
- owner: {_account_id: 1},
- };
- element._account = {_account_id: 1};
- element._loggedIn = true;
-
- flushAsynchronousOperations();
- const label = element.$.descriptionLabel;
- assert.equal(label.value, 'test');
- label.editing = true;
- label._inputText = 'test2';
- label._save();
- flushAsynchronousOperations();
- assert.isTrue(putDescStub.called);
- assert.equal(putDescStub.args[0][2], 'test2');
- });
-
test('_updateRebaseAction', () => {
const currentRevisionActions = {
cherrypick: {
@@ -572,106 +446,6 @@
assert.equal(element._numFilesShown, 200);
});
- test('patch num change', done => {
- element._changeNum = '42';
- element._patchRange = {
- basePatchNum: 'PARENT',
- patchNum: 2,
- };
- element._change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- revisions: {
- rev2: {_number: 2},
- rev1: {_number: 1},
- rev13: {_number: 13},
- rev3: {_number: 3},
- },
- current_revision: 'rev3',
- status: 'NEW',
- labels: {},
- };
- element.viewState.diffMode = 'UNIFIED';
- flushAsynchronousOperations();
-
- const selectEl = element.$$('.patchInfo-header gr-select');
- assert.ok(selectEl);
- const optionEls = Polymer.dom(element.root).querySelectorAll(
- '.patchInfo-header option');
- assert.equal(optionEls.length, 4);
- const select = element.$$('.patchInfo-header #patchSetSelect').bindValue;
- assert.notEqual(select, 1);
- assert.equal(select, 2);
- assert.notEqual(select, 3);
- assert.equal(optionEls[3].value, 13);
-
- let numEvents = 0;
- selectEl.addEventListener('change', e => {
- assert.equal(element.viewState.diffMode, 'UNIFIED');
- numEvents++;
- if (numEvents == 1) {
- assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
- element._change, '1', 'PARENT'));
- selectEl.nativeSelect.value = '3';
- element.fire('change', {}, {node: selectEl.nativeSelect});
- } else if (numEvents == 2) {
- assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
- element._change, '3', 'PARENT'));
- done();
- }
- });
- selectEl.nativeSelect.value = '1';
- element.fire('change', {}, {node: selectEl.nativeSelect});
- });
-
- test('patch num change with missing current_revision', done => {
- element._changeNum = '42';
- element._patchRange = {
- basePatchNum: 'PARENT',
- patchNum: 2,
- };
- element._change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- revisions: {
- rev2: {_number: 2},
- rev1: {_number: 1},
- rev13: {_number: 13},
- rev3: {_number: 3},
- },
- status: 'NEW',
- labels: {},
- };
- flushAsynchronousOperations();
- const selectEl = element.$$('.patchInfo-header gr-select');
- assert.ok(selectEl);
- const optionEls = Polymer.dom(element.root).querySelectorAll(
- '.patchInfo-header option');
- assert.equal(optionEls.length, 4);
- assert.notEqual(
- element.$$('.patchInfo-header #patchSetSelect').bindValue, 1);
- assert.equal(
- element.$$('.patchInfo-header #patchSetSelect').bindValue, 2);
- assert.notEqual(
- element.$$('.patchInfo-header #patchSetSelect').bindValue, 3);
- assert.equal(optionEls[3].value, 13);
-
- let numEvents = 0;
- selectEl.addEventListener('change', e => {
- numEvents++;
- if (numEvents == 1) {
- assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
- element._change, '1', 'PARENT'));
- selectEl.nativeSelect.value = '3';
- element.fire('change', {}, {node: selectEl.nativeSelect});
- } else if (numEvents == 2) {
- assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
- element._change, '3', 'PARENT'));
- done();
- }
- });
- selectEl.nativeSelect.value = '1';
- element.fire('change', {}, {node: selectEl.nativeSelect});
- });
-
test('diffMode defaults to side by side without preferences', done => {
sandbox.stub(element.$.restAPI, 'getPreferences').returns(
Promise.resolve({}));
@@ -703,101 +477,6 @@
});
});
- test('diff against dropdown', done => {
- element._changeNum = '42';
- element._patchRange = {
- basePatchNum: 'PARENT',
- patchNum: '3',
- };
- element._change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- revisions: {
- rev1: {_number: 1},
- rev2: {_number: 2},
- rev3: {_number: 'edit', basePatchNum: 2},
- rev4: {_number: 3},
- },
- status: 'NEW',
- labels: {},
- };
-
- flush(() => {
- const selectEl = element.$.patchChange;
- assert.equal(selectEl.nativeSelect.value, 'PARENT');
- assert.isTrue(element.$$('#patchChange option[value="3"]')
- .hasAttribute('disabled'));
- selectEl.addEventListener('change', () => {
- assert.equal(selectEl.nativeSelect.value, 'edit');
- assert(navigateToChangeStub.lastCall.calledWithExactly(
- element._change, '3', 'edit'),
- 'Should navigate to /c/42/edit..3');
- done();
- });
- selectEl.nativeSelect.value = 'edit';
- element.fire('change', {}, {node: selectEl.nativeSelect});
- });
- });
-
- test('expandAllDiffs called when expand button clicked', () => {
- element._shownFileCount = 1;
- flushAsynchronousOperations();
- sandbox.stub(element.$.fileList, 'expandAllDiffs');
- MockInteractions.tap(Polymer.dom(element.root).querySelector(
- '#expandBtn'));
- assert.isTrue(element.$.fileList.expandAllDiffs.called);
- });
-
- test('collapseAllDiffs called when expand button clicked', () => {
- element._shownFileCount = 1;
- flushAsynchronousOperations();
- sandbox.stub(element.$.fileList, 'collapseAllDiffs');
- MockInteractions.tap(Polymer.dom(element.root).querySelector(
- '#collapseBtn'));
- assert.isTrue(element.$.fileList.collapseAllDiffs.called);
- });
-
- test('show/hide diffs disabled for large amounts of files', done => {
- const computeSpy = sandbox.spy(element, '_fileListActionsVisible');
- element._files = [];
- element.changeNum = '42';
- element.patchRange = {
- basePatchNum: 'PARENT',
- patchNum: '2',
- };
- element._shownFileCount = 1;
- flush(() => {
- assert.isTrue(computeSpy.lastCall.returnValue);
- _.times(element._maxFilesForBulkActions + 1, () => {
- element._shownFileCount = element._shownFileCount + 1;
- });
- assert.isFalse(computeSpy.lastCall.returnValue);
- done();
- });
- });
-
- test('diff mode selector initializes from preferences', () => {
- let resolvePrefs;
- const prefsPromise = new Promise(resolve => {
- resolvePrefs = resolve;
- });
- sandbox.stub(element.$.restAPI, 'getPreferences').returns(prefsPromise);
-
- // Attach a new gr-change-view so we can intercept the preferences fetch.
- const view = document.createElement('gr-change-view');
- const select = view.$.modeSelect;
- fixture('blank').appendChild(view);
- flushAsynchronousOperations();
-
- // At this point the diff mode doesn't yet have the user's preference.
- assert.equal(select.nativeSelect.value, 'SIDE_BY_SIDE');
-
- // Receive the overriding preference.
- resolvePrefs({default_diff_view: 'UNIFIED'});
- flushAsynchronousOperations();
- assert.equal(select.nativeSelect.value, 'SIDE_BY_SIDE');
- document.getElementById('blank').restore();
- });
-
test('don’t reload entire page when patchRange changes', () => {
const reloadStub = sandbox.stub(element, '_reload',
() => { return Promise.resolve(); });
@@ -841,35 +520,6 @@
assert.isTrue(collapseStub.calledTwice);
});
- test('include base patch when not parent', () => {
- element._changeNum = '42';
- element._patchRange = {
- basePatchNum: '2',
- patchNum: '3',
- };
- element._change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- revisions: {
- rev2: {_number: 2},
- rev1: {_number: 1},
- rev13: {_number: 13},
- rev3: {_number: 3},
- },
- status: 'NEW',
- labels: {},
- };
-
- element._changePatchNum(13, 2);
- assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
- element._change, 13, 2));
-
- element._patchRange.basePatchNum = 'PARENT';
-
- element._changePatchNum(3, 'PARENT');
- assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
- element._change, 3, 'PARENT'));
- });
-
test('related changes are updated and new patch selected after rebase',
done => {
element._changeNum = '42';
@@ -888,7 +538,6 @@
test('related changes are not updated after other action', done => {
sandbox.stub(element, '_reload', () => { return Promise.resolve(); });
- sandbox.stub(element, '_updateSelected');
sandbox.stub(element.$.relatedChanges, 'reload');
const e = {detail: {action: 'abandon'}};
element._handleReloadChange(e).then(() => {
@@ -1092,15 +741,6 @@
'_openReplyDialog should have been passed CCS');
});
- test('class is applied to file list on old patch set', () => {
- const allPatchSets = [{num: 1}, {num: 2}, {num: 4}];
- assert.equal(element._computePatchInfoClass('1', allPatchSets),
- 'patchInfoOldPatchSet');
- assert.equal(element._computePatchInfoClass('2', allPatchSets),
- 'patchInfoOldPatchSet');
- assert.equal(element._computePatchInfoClass('4', allPatchSets), '');
- });
-
test('getUrlParameter functionality', () => {
const locationStub = sandbox.stub(element, '_getLocationSearch');
@@ -1510,30 +1150,14 @@
assert.equal(element._patchRange.patchNum, 'baz');
});
- suite('editLoaded behavior', () => {
- setup(() => {
- element._loggedIn = true;
- element._diffPrefs = {};
- });
+ test('_editLoaded set when patchNum is an edit', () => {
+ sandbox.stub(element, 'computeLatestPatchNum').returns('2');
+ element._patchRange = {patchNum: element.EDIT_NAME};
- const isVisible = el => {
- assert.ok(el);
- return getComputedStyle(el).getPropertyValue('display') !== 'none';
- };
+ assert.isTrue(element._editLoaded);
+ element.set('_patchRange.patchNum', 1);
- test('patch specific elements', () => {
- sandbox.stub(element, 'computeLatestPatchNum').returns('2');
- element._patchRange = {patchNum: element.EDIT_NAME};
- flushAsynchronousOperations();
-
- assert.isFalse(isVisible(element.$.diffPrefsContainer));
- assert.isFalse(isVisible(element.$$('.descriptionContainer')));
- element.set('_patchRange.patchNum', 1);
- flushAsynchronousOperations();
-
- assert.isTrue(isVisible(element.$$('.descriptionContainer')));
- assert.isTrue(isVisible(element.$.diffPrefsContainer));
- });
+ assert.isFalse(element._editLoaded);
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
new file mode 100644
index 0000000..edd5389
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -0,0 +1,226 @@
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
+<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../../shared/gr-select/gr-select.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+
+
+<dom-module id="gr-file-list-header">
+ <template>
+ <style include="shared-styles">
+ .prefsButton {
+ float: right;
+ }
+ .collapseToggleButton {
+ text-decoration: none;
+ }
+ .patchInfoEdit.patchInfo-header {
+ background-color: #fcfad6;
+ }
+ .patchInfoOldPatchSet.patchInfo-header {
+ background-color: #fff9c4;
+ }
+ .patchInfoOldPatchSet .latestPatchContainer {
+ display: initial;
+ }
+ .patchInfo-header {
+ padding: .5em calc(var(--default-horizontal-margin) / 2);
+ }
+ .patchInfo-header {
+ background-color: #f6f6f6;
+ border-bottom: 1px solid #ebebeb;
+ display: flex;
+ justify-content: space-between;
+ }
+ .latestPatchContainer {
+ display: none;
+ }
+ .patchSetSelect {
+ max-width: 8em;
+ }
+ gr-editable-label.descriptionLabel {
+ max-width: 100%;
+ }
+ .mobile {
+ display: none;
+ }
+ #diffPrefsContainer,
+ .rightControls {
+ margin: auto 0 auto auto;
+ }
+ .patchInfo-header-wrapper {
+ width: 100%;
+ }
+ .showOnEdit {
+ display: none;
+ }
+ .editLoaded .hideOnEdit {
+ display: none;
+ }
+ .editLoaded .showOnEdit {
+ display: initial;
+ }
+ .fileList-header {
+ display: flex;
+ font-weight: bold;
+ justify-content: space-between;
+ margin: .5em calc(var(--default-horizontal-margin) / 2);
+ }
+ .rightControls {
+ display: flex;
+ flex-wrap: wrap;
+ font-weight: normal;
+ justify-content: flex-end;
+ }
+ .separator {
+ margin: 0 .25em;
+ }
+ .expandInline {
+ padding-right: .25em;
+ }
+ .patchSetSelect {
+ max-width: 8em;
+ }
+ .editLoaded .hideOnEdit {
+ display: none;
+ }
+ .editLoaded .showOnEdit {
+ display: initial;
+ }
+ </style>
+ <div class$="patchInfo-header [[_computeEditLoadedClass(editLoaded)]] [[_computePatchInfoClass(patchRange.patchNum, allPatchSets)]]">
+ <div class="patchInfo-header-wrapper">
+ <label class="patchSelectLabel" for="patchSetSelect">
+ Patch set
+ </label>
+ <gr-select
+ id="patchSetSelect"
+ bind-value="{{_selectedPatchSet}}"
+ class="patchSetSelect"
+ on-change="_handlePatchChange">
+ <select>
+ <template is="dom-repeat" items="[[allPatchSets]]"
+ as="patchNum">
+ <option
+ value$="[[patchNum.num]]"
+ disabled$="[[_computePatchSetDisabled(patchNum.num, patchRange.basePatchNum, revisions)]]">
+ [[patchNum.num]]
+ /
+ [[computeLatestPatchNum(allPatchSets)]]
+ [[_computePatchSetCommentsString(comments, patchNum.num)]]
+ [[_computePatchSetDescription(change, patchNum.num)]]
+ </option>
+ </template>
+ </select>
+ </gr-select>
+ /
+ <gr-commit-info
+ change="[[change]]"
+ server-config="[[serverConfig]]"
+ commit-info="[[commitInfo]]"></gr-commit-info>
+ <span class="latestPatchContainer">
+ /
+ <a href$="[[changeUrl]]">Go to latest patch set</a>
+ </span>
+ <span class="downloadContainer desktop">
+ /
+ <gr-button link
+ class="download"
+ on-tap="_handleDownloadTap">Download</gr-button>
+ </span>
+ <span class="descriptionContainer hideOnEdit">
+ /
+ <gr-editable-label
+ id="descriptionLabel"
+ class="descriptionLabel"
+ value="[[_computePatchSetDescription(change, _selectedPatchSet)]]"
+ placeholder="[[_computeDescriptionPlaceholder(_descriptionReadOnly)]]"
+ read-only="[[_descriptionReadOnly]]"
+ on-changed="_handleDescriptionChanged"></gr-editable-label>
+ </span>
+ <span id="diffPrefsContainer"
+ class="hideOnEdit"
+ hidden$="[[_computePrefsButtonHidden(diffPrefs, loggedIn)]]"
+ hidden>
+ <gr-button link
+ class="prefsButton desktop"
+ on-tap="_handlePrefsTap">Diff Preferences</gr-button>
+ </span>
+ </div>
+ </div>
+ <div class="fileList-header">
+ <div>Files</div>
+ <div class="rightControls">
+ <template is="dom-if"
+ if="[[_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
+ <gr-button
+ id="expandBtn"
+ link
+ on-tap="_expandAllDiffs">Show diffs</gr-button>
+ <span class="separator">/</span>
+ <gr-button
+ id="collapseBtn"
+ link
+ on-tap="_collapseAllDiffs">Hide diffs</gr-button>
+ </template>
+ <template is="dom-if"
+ if="[[!_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
+ <div class="warning">
+ Bulk actions disabled because there are too many files.
+ </div>
+ </template>
+ <span class="separator">/</span>
+ <gr-select
+ id="modeSelect"
+ bind-value="{{diffViewMode}}">
+ <select>
+ <option value="SIDE_BY_SIDE">Side By Side</option>
+ <option value="UNIFIED_DIFF">Unified</option>
+ </select>
+ </gr-select>
+ <span class="separator">/</span>
+ <label>
+ Diff against
+ <gr-select id="patchChange" bind-value="{{_diffAgainst}}"
+ class="patchSetSelect" on-change="_handleBasePatchChange">
+ <select>
+ <option value="PARENT">Base</option>
+ <template
+ is="dom-repeat"
+ items="[[allPatchSets]]"
+ as="patchNum">
+ <option
+ disabled$="[[_computeBasePatchDisabled(patchNum.num, patchRange.patchNum, revisions)]]"
+ value$="[[patchNum.num]]">
+ [[patchNum.num]]
+ [[patchNum.desc]]
+ </option>
+ </template>
+ </select>
+ </gr-select>
+ </label>
+ </div>
+ </div>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+ <script src="gr-file-list-header.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
new file mode 100644
index 0000000..4d56cd8
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
@@ -0,0 +1,264 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ // Maximum length for patch set descriptions.
+ const PATCH_DESC_MAX_LENGTH = 500;
+
+ Polymer({
+ is: 'gr-file-list-header',
+
+ properties: {
+ account: Object,
+ allPatchSets: Array,
+ change: Object,
+ changeNum: String,
+ changeUrl: String,
+ comments: Object,
+ commitInfo: Object,
+ editLoaded: Boolean,
+ loggedIn: Boolean,
+ serverConfig: Object,
+ shownFileCount: Number,
+ diffPrefs: Object,
+ diffViewMode: String,
+ /** @type {?} */
+ patchRange: {
+ type: Object,
+ observer: 'updateSelected',
+ },
+ revisions: Array,
+ // Caps the number of files that can be shown and have the 'show diffs' /
+ // 'hide diffs' buttons still be functional.
+ _maxFilesForBulkActions: {
+ type: Number,
+ readOnly: true,
+ value: 225,
+ },
+ _descriptionReadOnly: {
+ type: Boolean,
+ computed: '_computeDescriptionReadOnly(loggedIn, change, account)',
+ },
+ _selectedPatchSet: String,
+ _diffAgainst: String,
+ },
+
+ behaviors: [
+ Gerrit.PatchSetBehavior,
+ ],
+
+ _expandAllDiffs() {
+ this.fire('expand-diffs');
+ },
+
+ _collapseAllDiffs() {
+ this.fire('collapse-diffs');
+ },
+
+ updateSelected() {
+ this._selectedPatchSet = this.patchRange.patchNum;
+ this._diffAgainst = this.patchRange.basePatchNum;
+ },
+
+ _computeDescriptionPlaceholder(readOnly) {
+ return (readOnly ? 'No' : 'Add a') + ' patch set description';
+ },
+
+ _computeDescriptionReadOnly(loggedIn, change, account) {
+ return !(loggedIn && (account._account_id === change.owner._account_id));
+ },
+
+ _computePatchSetDescription(change, patchNum) {
+ const rev = this.getRevisionByPatchNum(change.revisions, patchNum);
+ return (rev && rev.description) ?
+ rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
+ },
+
+
+ /**
+ * @param {!Object} revisions The revisions object keyed by revision hashes
+ * @param {?Object} patchSet A revision already fetched from {revisions}
+ * @return {string|undefined} the SHA hash corresponding to the revision.
+ */
+ _getPatchsetHash(revisions, patchSet) {
+ for (const rev in revisions) {
+ if (revisions.hasOwnProperty(rev) &&
+ revisions[rev] === patchSet) {
+ return rev;
+ }
+ }
+ },
+
+ _handleDescriptionChanged(e) {
+ const desc = e.detail.trim();
+ const rev = this.getRevisionByPatchNum(this.change.revisions,
+ this._selectedPatchSet);
+ const sha = this._getPatchsetHash(this.change.revisions, rev);
+ this.$.restAPI.setDescription(this.changeNum,
+ this._selectedPatchSet, desc)
+ .then(res => {
+ if (res.ok) {
+ this.set(['_change', 'revisions', sha, 'description'], desc);
+ }
+ });
+ },
+
+ _computeBasePatchDisabled(patchNum, currentPatchNum) {
+ return this.findSortedIndex(patchNum, this.revisions) >=
+ this.findSortedIndex(currentPatchNum, this.revisions);
+ },
+
+ _computePrefsButtonHidden(prefs, loggedIn) {
+ return !loggedIn || !prefs;
+ },
+
+ // Copied from gr-file-list
+ _getCommentsForPath(comments, patchNum, path) {
+ return (comments[path] || []).filter(c => {
+ return this.patchNumEquals(c.patch_set, patchNum);
+ });
+ },
+
+ // Copied from gr-file-list
+ _computeUnresolvedNum(comments, drafts, patchNum, path) {
+ comments = this._getCommentsForPath(comments, patchNum, path);
+ drafts = this._getCommentsForPath(drafts, patchNum, path);
+ comments = comments.concat(drafts);
+
+ // Create an object where every comment ID is the key of an unresolved
+ // comment.
+
+ const idMap = comments.reduce((acc, comment) => {
+ if (comment.unresolved) {
+ acc[comment.id] = true;
+ }
+ return acc;
+ }, {});
+
+ // Set false for the comments that are marked as parents.
+ for (const comment of comments) {
+ idMap[comment.in_reply_to] = false;
+ }
+
+ // The unresolved comments are the comments that still have true.
+ const unresolvedLeaves = Object.keys(idMap).filter(key => {
+ return idMap[key];
+ });
+
+ return unresolvedLeaves.length;
+ },
+
+ _computePatchSetCommentsString(allComments, patchNum) {
+ let numComments = 0;
+ let numUnresolved = 0;
+ for (const file in allComments) {
+ if (allComments.hasOwnProperty(file)) {
+ numComments += this._getCommentsForPath(
+ allComments, patchNum, file).length;
+ numUnresolved += this._computeUnresolvedNum(
+ allComments, {}, patchNum, file);
+ }
+ }
+ let commentsStr = '';
+ if (numComments > 0) {
+ commentsStr = '(' + numComments + ' comments';
+ if (numUnresolved > 0) {
+ commentsStr += ', ' + numUnresolved + ' unresolved';
+ }
+ commentsStr += ')';
+ }
+ return commentsStr;
+ },
+
+ _fileListActionsVisible(shownFileCount, maxFilesForBulkActions) {
+ return shownFileCount <= maxFilesForBulkActions;
+ },
+
+ /**
+ * Determines if a patch number should be disabled based on value of the
+ * basePatchNum from gr-file-list.
+ * @param {number} patchNum Patch number available in dropdown
+ * @param {number|string} basePatchNum Base patch number from file list
+ * @return {boolean}
+ */
+ _computePatchSetDisabled(patchNum, basePatchNum) {
+ if (basePatchNum === 'PARENT') { return false; }
+
+ return this.findSortedIndex(patchNum, this.revisions) <=
+ this.findSortedIndex(basePatchNum, this.revisions);
+ },
+
+ /**
+ * Change active patch to the provided patch num.
+ * @param {number|string} basePatchNum the base patch to be viewed.
+ * @param {number|string} patchNum the patch number to be viewed.
+ * @param {boolean} opt_forceParams When set to true, the resulting URL will
+ * always include the patch range, even if the requested patchNum is
+ * known to be the latest.
+ */
+ _changePatchNum(patchNum, basePatchNum, opt_forceParams) {
+ if (!opt_forceParams) {
+ let currentPatchNum;
+ if (this.change.current_revision) {
+ currentPatchNum =
+ this.change.revisions[this.change.current_revision]._number;
+ } else {
+ currentPatchNum = this.computeLatestPatchNum(this.allPatchSets);
+ }
+ if (this.patchNumEquals(patchNum, currentPatchNum) &&
+ basePatchNum === 'PARENT') {
+ Gerrit.Nav.navigateToChange(this.change);
+ return;
+ }
+ }
+ Gerrit.Nav.navigateToChange(this.change, patchNum,
+ basePatchNum);
+ },
+
+ _handleBasePatchChange(e) {
+ this._changePatchNum(this._selectedPatchSet, e.target.value, true);
+ },
+
+ _handlePatchChange(e) {
+ this._changePatchNum(e.target.value, this._diffAgainst, true);
+ },
+
+ _handlePrefsTap(e) {
+ e.preventDefault();
+ this.fire('open-diff-prefs');
+ },
+
+ _handleDownloadTap(e) {
+ e.preventDefault();
+ this.fire('open-download-dialog');
+ },
+
+ _computeEditLoadedClass(editLoaded) {
+ return editLoaded ? 'editLoaded' : '';
+ },
+
+ _computePatchInfoClass(patchNum, allPatchSets) {
+ if (this.patchNumEquals(patchNum, this.EDIT_NAME)) {
+ return 'patchInfoEdit';
+ }
+
+ const latestNum = this.computeLatestPatchNum(allPatchSets);
+ if (this.patchNumEquals(patchNum, latestNum)) {
+ return '';
+ }
+ return 'patchInfoOldPatchSet';
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
new file mode 100644
index 0000000..320e13d
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
@@ -0,0 +1,435 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-file-list-header</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<script src="../../../bower_components/page/page.js"></script>
+
+<link rel="import" href="gr-file-list-header.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-file-list-header></gr-file-list-header>
+ </template>
+</test-fixture>
+
+<test-fixture id="blank">
+ <template>
+ <div></div>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-file-list-header tests', () => {
+ let element;
+ let sandbox;
+ let navigateToChangeStub;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ navigateToChangeStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({test: 'config'}); },
+ getAccount() { return Promise.resolve(null); },
+ _fetchSharedCacheURL() { return Promise.resolve({}); },
+ });
+ element = fixture('basic');
+ });
+
+ teardown(done => {
+ flush(() => {
+ sandbox.restore();
+ done();
+ });
+ });
+
+ test('Diff preferences hidden when no prefs or logged out', () => {
+ element.loggedIn = false;
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element.loggedIn = true;
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element.loggedIn = false;
+ element.diffPrefs = {font_size: '12'};
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.diffPrefsContainer.hidden);
+
+ element.loggedIn = true;
+ flushAsynchronousOperations();
+ assert.isFalse(element.$.diffPrefsContainer.hidden);
+ });
+
+ test('_computeDescriptionReadOnly', () => {
+ assert.equal(element._computeDescriptionReadOnly(false,
+ {owner: {_account_id: 1}}, {_account_id: 1}), true);
+ assert.equal(element._computeDescriptionReadOnly(true,
+ {owner: {_account_id: 0}}, {_account_id: 1}), true);
+ assert.equal(element._computeDescriptionReadOnly(true,
+ {owner: {_account_id: 1}}, {_account_id: 1}), false);
+ });
+
+ test('_computeDescriptionPlaceholder', () => {
+ assert.equal(element._computeDescriptionPlaceholder(true),
+ 'No patch set description');
+ assert.equal(element._computeDescriptionPlaceholder(false),
+ 'Add a patch set description');
+ });
+
+ test('_computePatchSetDisabled', () => {
+ element.revisions = [
+ {_number: 1},
+ {_number: 2},
+ {_number: element.EDIT_NAME, basePatchNum: 2},
+ {_number: 3},
+ ];
+ let basePatchNum = 'PARENT';
+ let patchNum = 1;
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ false);
+ basePatchNum = 1;
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ true);
+ patchNum = 2;
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ false);
+ basePatchNum = element.EDIT_NAME;
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ true);
+ patchNum = '3';
+ assert.equal(element._computePatchSetDisabled(patchNum, basePatchNum),
+ false);
+ });
+
+ test('_computePatchSetCommentsString', () => {
+ // Test string with unresolved comments.
+
+ comments = {
+ foo: [{
+ id: '27dcee4d_f7b77cfa',
+ message: 'test',
+ patch_set: 1,
+ unresolved: true,
+ }],
+ bar: [{
+ id: '27dcee4d_f7b77cfa',
+ message: 'test',
+ patch_set: 1,
+ },
+ {
+ id: '27dcee4d_f7b77cfa',
+ message: 'test',
+ patch_set: 1,
+ }],
+ abc: [],
+ };
+
+ assert.equal(element._computePatchSetCommentsString(comments, 1),
+ '(3 comments, 1 unresolved)');
+
+ // Test string with no unresolved comments.
+ delete comments['foo'];
+ assert.equal(element._computePatchSetCommentsString(comments, 1),
+ '(2 comments)');
+
+ // Test string with no comments.
+ delete comments['bar'];
+ assert.equal(element._computePatchSetCommentsString(comments, 1), '');
+ });
+
+ test('_handleDescriptionChanged', () => {
+ const putDescStub = sandbox.stub(element.$.restAPI, 'setDescription')
+ .returns(Promise.resolve({ok: true}));
+ sandbox.stub(element, '_computeDescriptionReadOnly');
+
+ element.changeNum = '42';
+ element.patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: 1,
+ };
+ element._selectedPatchNum = '1';
+ element.change = {
+ change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+ revisions: {
+ rev1: {_number: 1, description: 'test', commit: {commit: 'rev1'}},
+ },
+ current_revision: 'rev1',
+ status: 'NEW',
+ labels: {},
+ actions: {},
+ owner: {_account_id: 1},
+ };
+ element.account = {_account_id: 1};
+ element.loggedIn = true;
+
+ flushAsynchronousOperations();
+ const label = element.$.descriptionLabel;
+ assert.equal(label.value, 'test');
+ label.editing = true;
+ label._inputText = 'test2';
+ label._save();
+ flushAsynchronousOperations();
+ assert.isTrue(putDescStub.called);
+ assert.equal(putDescStub.args[0][2], 'test2');
+ });
+
+ test('patch num change', done => {
+ element.patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: 2,
+ };
+
+ element.allPatchSets = [
+ {num: 1},
+ {num: 2},
+ {num: 3},
+ {num: 13},
+ ];
+
+ flushAsynchronousOperations();
+
+ const selectEl = element.$$('.patchInfo-header gr-select');
+ assert.ok(selectEl);
+ const optionEls = Polymer.dom(element.root).querySelectorAll(
+ '.patchInfo-header option');
+ assert.equal(optionEls.length, 4);
+ const select = element.$$('.patchInfo-header #patchSetSelect').bindValue;
+ assert.notEqual(select, 1);
+ assert.equal(select, 2);
+ assert.notEqual(select, 3);
+ assert.equal(optionEls[3].value, 13);
+
+ let numEvents = 0;
+ selectEl.addEventListener('change', e => {
+ assert.equal(element.diffViewMode, 'SIDE_BY_SIDE');
+ numEvents++;
+ if (numEvents == 1) {
+ assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
+ element.change, '1', 'PARENT'));
+ selectEl.nativeSelect.value = '3';
+ element.fire('change', {}, {node: selectEl.nativeSelect});
+ } else if (numEvents == 2) {
+ assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
+ element.change, '3', 'PARENT'));
+ done();
+ }
+ });
+ selectEl.nativeSelect.value = '1';
+ element.fire('change', {}, {node: selectEl.nativeSelect});
+ });
+
+ test('patch num change with missing current_revision', done => {
+ element.patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: 2,
+ };
+
+ element.allPatchSets = [
+ {num: 1},
+ {num: 2},
+ {num: 3},
+ {num: 13},
+ ];
+ flushAsynchronousOperations();
+ const selectEl = element.$$('.patchInfo-header gr-select');
+ assert.ok(selectEl);
+ const optionEls = Polymer.dom(element.root).querySelectorAll(
+ '.patchInfo-header option');
+ assert.equal(optionEls.length, 4);
+ assert.notEqual(
+ element.$$('.patchInfo-header #patchSetSelect').bindValue, 1);
+ assert.equal(
+ element.$$('.patchInfo-header #patchSetSelect').bindValue, 2);
+ assert.notEqual(
+ element.$$('.patchInfo-header #patchSetSelect').bindValue, 3);
+ assert.equal(optionEls[3].value, 13);
+
+ let numEvents = 0;
+ selectEl.addEventListener('change', e => {
+ numEvents++;
+ if (numEvents == 1) {
+ assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
+ element.change, '1', 'PARENT'));
+ selectEl.nativeSelect.value = '3';
+ element.fire('change', {}, {node: selectEl.nativeSelect});
+ } else if (numEvents == 2) {
+ assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
+ element.change, '3', 'PARENT'));
+ done();
+ }
+ });
+ selectEl.nativeSelect.value = '1';
+ element.fire('change', {}, {node: selectEl.nativeSelect});
+ });
+
+ test('diff against dropdown', done => {
+ element.revisions = [
+ {commit: {}},
+ {commit: {}},
+ {commit: {}},
+ {commit: {}},
+ ];
+ element.allPatchSets = [
+ {num: 1},
+ {num: 2},
+ {num: 3},
+ {num: 'edit'},
+ ];
+ element.patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: '3',
+ };
+
+ flush(() => {
+ const selectEl = element.$.patchChange;
+ assert.equal(selectEl.nativeSelect.value, 'PARENT');
+ assert.isTrue(element.$$('#patchChange option[value="3"]')
+ .hasAttribute('disabled'));
+ selectEl.addEventListener('change', () => {
+ assert.equal(selectEl.nativeSelect.value, 'edit');
+ assert(navigateToChangeStub.lastCall.calledWithExactly(
+ element.change, '3', 'edit'),
+ 'Should navigate to /c/42/edit..3');
+ done();
+ });
+ selectEl.nativeSelect.value = 'edit';
+ element.fire('change', {}, {node: selectEl.nativeSelect});
+ });
+ });
+
+ test('expandAllDiffs called when expand button clicked', () => {
+ element.shownFileCount = 1;
+ flushAsynchronousOperations();
+ sandbox.stub(element, '_expandAllDiffs');
+ MockInteractions.tap(Polymer.dom(element.root).querySelector(
+ '#expandBtn'));
+ assert.isTrue(element._expandAllDiffs.called);
+ });
+
+ test('collapseAllDiffs called when expand button clicked', () => {
+ element.shownFileCount = 1;
+ flushAsynchronousOperations();
+ sandbox.stub(element, '_collapseAllDiffs');
+ MockInteractions.tap(Polymer.dom(element.root).querySelector(
+ '#collapseBtn'));
+ assert.isTrue(element._collapseAllDiffs.called);
+ });
+
+ test('show/hide diffs disabled for large amounts of files', done => {
+ const computeSpy = sandbox.spy(element, '_fileListActionsVisible');
+ element._files = [];
+ element.changeNum = '42';
+ element.patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: '2',
+ };
+ element.shownFileCount = 1;
+ flush(() => {
+ assert.isTrue(computeSpy.lastCall.returnValue);
+ _.times(element._maxFilesForBulkActions + 1, () => {
+ element.shownFileCount = element.shownFileCount + 1;
+ });
+ assert.isFalse(computeSpy.lastCall.returnValue);
+ done();
+ });
+ });
+
+ test('diff mode selector is set correctly', () => {
+ const select = element.$.modeSelect;
+ element.diffViewMode = 'SIDE_BY_SIDE';
+ flushAsynchronousOperations();
+ assert.equal(select.nativeSelect.value, 'SIDE_BY_SIDE');
+
+ element.diffViewMode = 'UNIFIED_DIFF';
+ flushAsynchronousOperations();
+ assert.equal(select.nativeSelect.value, 'UNIFIED_DIFF');
+ });
+
+ test('include base patch when not parent', () => {
+ element.changeNum = '42';
+ element.patchRange = {
+ basePatchNum: '2',
+ patchNum: '3',
+ };
+ element.change = {
+ change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+ revisions: {
+ rev2: {_number: 2},
+ rev1: {_number: 1},
+ rev13: {_number: 13},
+ rev3: {_number: 3},
+ },
+ status: 'NEW',
+ labels: {},
+ };
+
+ element._changePatchNum(13, 2);
+ assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
+ element.change, 13, 2));
+
+ element.patchRange.basePatchNum = 'PARENT';
+
+ element._changePatchNum(3, 'PARENT');
+ assert.isTrue(navigateToChangeStub.lastCall.calledWithExactly(
+ element.change, 3, 'PARENT'));
+ });
+
+ test('class is applied to file list on old patch set', () => {
+ const allPatchSets = [{num: 1}, {num: 2}, {num: 4}];
+ assert.equal(element._computePatchInfoClass('1', allPatchSets),
+ 'patchInfoOldPatchSet');
+ assert.equal(element._computePatchInfoClass('2', allPatchSets),
+ 'patchInfoOldPatchSet');
+ assert.equal(element._computePatchInfoClass('4', allPatchSets), '');
+ });
+
+ suite('editLoaded behavior', () => {
+ setup(() => {
+ element.loggedIn = true;
+ element.diffPrefs = {};
+ });
+
+ const isVisible = el => {
+ assert.ok(el);
+ return getComputedStyle(el).getPropertyValue('display') !== 'none';
+ };
+
+ test('patch specific elements', () => {
+ element.editLoaded = true;
+ sandbox.stub(element, 'computeLatestPatchNum').returns('2');
+ flushAsynchronousOperations();
+
+ assert.isFalse(isVisible(element.$.diffPrefsContainer));
+ assert.isFalse(isVisible(element.$$('.descriptionContainer')));
+
+ element.editLoaded = false;
+ flushAsynchronousOperations();
+
+ assert.isTrue(isVisible(element.$$('.descriptionContainer')));
+ assert.isTrue(isVisible(element.$.diffPrefsContainer));
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 912b0ff..38755c4 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -68,6 +68,7 @@
'change/gr-label-scores/gr-label-scores_test.html',
'change/gr-label-score-row/gr-label-score-row_test.html',
'change/gr-file-list/gr-file-list_test.html',
+ 'change/gr-file-list-header/gr-file-list-header_test.html',
'change/gr-message/gr-message_test.html',
'change/gr-messages-list/gr-messages-list_test.html',
'change/gr-related-changes-list/gr-related-changes-list_test.html',