Separate file list header into new component
Change-Id: I45619dfaaf005a89f8de6d3e9dc39a2bcbca1893
This commit is contained in:
@@ -21,12 +21,10 @@ limitations under the License.
|
||||
<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 @@ limitations under the License.
|
||||
<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 @@ limitations under the License.
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.prefsButton {
|
||||
float: right;
|
||||
}
|
||||
gr-change-star {
|
||||
margin-right: .25em;
|
||||
vertical-align: -.425em;
|
||||
@@ -152,45 +148,10 @@ limitations under the License.
|
||||
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 @@ limitations under the License.
|
||||
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 @@ limitations under the License.
|
||||
.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 @@ limitations under the License.
|
||||
<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 @@ limitations under the License.
|
||||
</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"
|
||||
|
@@ -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;
|
||||
|
||||
@@ -134,18 +132,10 @@
|
||||
type: String,
|
||||
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' : '';
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
@@ -171,6 +171,20 @@ limitations under the License.
|
||||
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 @@ limitations under the License.
|
||||
});
|
||||
});
|
||||
|
||||
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 @@ limitations under the License.
|
||||
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 @@ limitations under the License.
|
||||
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 @@ limitations under the License.
|
||||
});
|
||||
});
|
||||
|
||||
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 @@ limitations under the License.
|
||||
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 @@ limitations under the License.
|
||||
|
||||
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 @@ limitations under the License.
|
||||
'_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 @@ limitations under the License.
|
||||
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>
|
||||
|
@@ -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>
|
@@ -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';
|
||||
},
|
||||
});
|
||||
})();
|
@@ -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>
|
@@ -68,6 +68,7 @@ limitations under the License.
|
||||
'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',
|
||||
|
Reference in New Issue
Block a user