Merge "Add no permissions message to reply dialog labels"
diff --git a/Documentation/dev-note-db.txt b/Documentation/dev-note-db.txt
index ef35d7d..0db3785 100644
--- a/Documentation/dev-note-db.txt
+++ b/Documentation/dev-note-db.txt
@@ -45,8 +45,8 @@
Account and group data is migrated to NoteDb automatically using the normal
schema upgrade process during updates. The remainder of this section details the
-configuration options that control migration of the change data, which is an
-ongoing process.
+configuration options that control migration of the change data, which is mostly
+but not fully implemented.
Change migration state is configured in `gerrit.config` with options like
`noteDb.changes.*`. These options are undocumented outside of this file, and the
diff --git a/WORKSPACE b/WORKSPACE
index 1ffe850..891e407e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -863,13 +863,13 @@
maven_jar(
name = "codemirror_minified",
artifact = "org.webjars.npm:codemirror-minified:" + CM_VERSION,
- sha1 = "3e8767c9293614968176fcf66cb873d6eb8b3051",
+ sha1 = "27d5d8902b0c08c049f429575ff4f931e29d1664",
)
maven_jar(
name = "codemirror_original",
artifact = "org.webjars.npm:codemirror:" + CM_VERSION,
- sha1 = "879c49085a44f062554a4e4a9ac248b7083d37cf",
+ sha1 = "76088a0cdf869ae0935821ba6720b2e0ed3e9108",
)
maven_jar(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 78fd32f..8413b5a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -398,7 +398,7 @@
}
/** @return true if this user can remove a reviewer for a change. */
- public boolean canRemoveReviewer() {
+ boolean canRemoveReviewer() {
return canPerform(Permission.REMOVE_REVIEWER);
}
diff --git a/lib/codemirror/cm.bzl b/lib/codemirror/cm.bzl
index 168ab33..fd56354 100644
--- a/lib/codemirror/cm.bzl
+++ b/lib/codemirror/cm.bzl
@@ -214,7 +214,7 @@
"z80",
]
-CM_VERSION = "5.22.0"
+CM_VERSION = "5.24.2"
TOP = "META-INF/resources/webjars/codemirror/%s" % CM_VERSION
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index c320fa1..05306ae 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -96,7 +96,7 @@
data-action-key$="[[action.__key]]"
data-action-type$="[[action.__type]]"
data-label$="[[action.label]]"
- disabled$="[[!action.enabled]]"
+ disabled$="[[_calculateDisabled(action, _hasKnownChainState)]]"
on-tap="_handleActionTap"></gr-button>
</template>
</section>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 8a4a5e9..2b0916d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -126,10 +126,17 @@
];
},
},
+ _hasKnownChainState: {
+ type: Boolean,
+ value: false,
+ },
changeNum: String,
changeStatus: String,
commitNum: String,
- hasParent: Boolean,
+ hasParent: {
+ type: Boolean,
+ observer: '_computeChainState',
+ },
patchNum: String,
commitMessage: {
type: String,
@@ -489,6 +496,22 @@
return key === '/' ? key : '/' + key;
},
+ /**
+ * Returns true if hasParent is defined (can be either true or false).
+ * returns false otherwise.
+ * @return {boolean} hasParent
+ */
+ _computeChainState: function(hasParent) {
+ this._hasKnownChainState = true;
+ },
+
+ _calculateDisabled: function(action, hasKnownChainState) {
+ if (action.__key === 'rebase' && hasKnownChainState === false) {
+ return true;
+ }
+ return !action.enabled;
+ },
+
_handleConfirmDialogCancel: function() {
this._hideAllDialogs();
},
@@ -503,19 +526,8 @@
},
_handleRebaseConfirm: function() {
- var payload = {};
var el = this.$.confirmRebase;
- if (el.clearParent) {
- // There is a subtle but important difference between setting the base
- // to an empty string and omitting it entirely from the payload. An
- // empty string implies that the parent should be cleared and the
- // change should be rebased on top of the target branch. Leaving out
- // the base implies that it should be rebased on top of its current
- // parent.
- payload.base = '';
- } else if (el.base && el.base.length > 0) {
- payload.base = el.base;
- }
+ var payload = {base: el.base};
this.$.overlay.close();
el.hidden = true;
this._fireAction('/rebase', this.revisionActions.rebase, true, payload);
@@ -617,47 +629,50 @@
},
_handleResponse: function(action, response) {
+ if (!response) { return; }
return this.$.restAPI.getResponseObject(response).then(function(obj) {
- switch (action.__key) {
- case ChangeActions.REVERT:
- this._setLabelValuesOnRevert(obj.change_id);
- /* falls through */
- case RevisionActions.CHERRYPICK:
- page.show(this.changePath(obj._number));
- break;
- case ChangeActions.DELETE:
- case RevisionActions.DELETE:
- if (action.__type === ActionType.CHANGE) {
- page.show('/');
- } else {
- page.show(this.changePath(this.changeNum));
- }
- break;
- default:
- this.dispatchEvent(new CustomEvent('reload-change',
- {detail: {action: action.__key}, bubbles: false}));
- break;
- }
+ switch (action.__key) {
+ case ChangeActions.REVERT:
+ this._setLabelValuesOnRevert(obj.change_id);
+ /* falls through */
+ case RevisionActions.CHERRYPICK:
+ page.show(this.changePath(obj._number));
+ break;
+ case ChangeActions.DELETE:
+ case RevisionActions.DELETE:
+ if (action.__type === ActionType.CHANGE) {
+ page.show('/');
+ } else {
+ page.show(this.changePath(this.changeNum));
+ }
+ break;
+ default:
+ this.dispatchEvent(new CustomEvent('reload-change',
+ {detail: {action: action.__key}, bubbles: false}));
+ break;
+ }
}.bind(this));
},
_handleResponseError: function(response) {
- if (response.ok) { return response; }
-
return response.text().then(function(errText) {
- alert('Could not perform action: ' + errText);
- throw Error(errText);
- });
+ this.fire('show-alert',
+ { message: 'Could not perform action: ' + errText });
+ if (errText.indexOf('Change is already up to date') !== 0) {
+ throw Error(errText);
+ }
+ }.bind(this));
},
_send: function(method, payload, actionEndpoint, revisionAction,
- cleanupFn) {
+ cleanupFn, opt_errorFn) {
var url = this.$.restAPI.getChangeActionURL(this.changeNum,
revisionAction ? this.patchNum : null, actionEndpoint);
- return this.$.restAPI.send(method, url, payload).then(function(response) {
- cleanupFn.call(this);
- return response;
- }.bind(this)).then(this._handleResponseError.bind(this));
+ return this.$.restAPI.send(method, url, payload,
+ this._handleResponseError, this).then(function(response) {
+ cleanupFn.call(this);
+ return response;
+ }.bind(this));
},
_handleAbandonTap: function() {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 9d27ddc..c0293ad 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -252,6 +252,33 @@
});
});
+ test('chain state', function() {
+ assert.equal(element._hasKnownChainState, false);
+ element.hasParent = true;
+ assert.equal(element._hasKnownChainState, true);
+ element.hasParent = false;
+ });
+
+ test('_calculateDisabled', function() {
+ var hasKnownChainState = false;
+ var action = {__key: 'rebase', enabled: true};
+ assert.equal(
+ element._calculateDisabled(action, hasKnownChainState), true);
+
+ action.__key = 'delete';
+ assert.equal(
+ element._calculateDisabled(action, hasKnownChainState), false);
+
+ action.__key = 'rebase';
+ hasKnownChainState = true;
+ assert.equal(
+ element._calculateDisabled(action, hasKnownChainState), false);
+
+ action.enabled = false;
+ assert.equal(
+ element._calculateDisabled(action, hasKnownChainState), true);
+ });
+
test('rebase change', function(done) {
var fireActionStub = sinon.stub(element, '_fireAction');
flush(function() {
@@ -266,18 +293,20 @@
method: 'POST',
title: 'Rebase onto tip of branch or parent change',
};
+ // rebase on other
element.$.confirmRebase.base = '1234';
element._handleRebaseConfirm();
assert.deepEqual(fireActionStub.lastCall.args,
['/rebase', rebaseAction, true, {base: '1234'}]);
- element.$.confirmRebase.base = '';
+ // rebase on parent
+ element.$.confirmRebase.base = null;
element._handleRebaseConfirm();
assert.deepEqual(fireActionStub.lastCall.args,
- ['/rebase', rebaseAction, true, {}]);
+ ['/rebase', rebaseAction, true, {base: null}]);
- element.$.confirmRebase.base = 'does not matter';
- element.$.confirmRebase.clearParent = true;
+ // rebase on tip
+ element.$.confirmRebase.base = '';
element._handleRebaseConfirm();
assert.deepEqual(fireActionStub.lastCall.args,
['/rebase', rebaseAction, true, {base: ''}]);
@@ -288,6 +317,7 @@
});
test('two dialogs are not shown at the same time', function(done) {
+ element._hasKnownChainState = true;
flush(function() {
var rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
assert.ok(rebaseButton);
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
index 129e325..c53a741 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
@@ -31,7 +31,7 @@
label {
cursor: pointer;
}
- .info {
+ .message {
font-style: italic;
}
.parentRevisionContainer label,
@@ -43,45 +43,66 @@
.parentRevisionContainer label {
margin-bottom: .2em;
}
- .clearParentContainer {
+ .rebaseOption {
margin: .5em 0;
}
</style>
<gr-confirm-dialog
confirm-label="Rebase"
on-confirm="_handleConfirmTap"
- on-cancel="_handleCancelTap"
- disabled="[[!valueSelected]]">
+ on-cancel="_handleCancelTap">
<div class="header">Confirm rebase</div>
<div class="main">
- <div class="parentRevisionContainer">
- <label for="parentInput">
- Parent revision
- <span id="optionalText" hidden$="[[!rebaseOnCurrent]]"> (optional)
- </span>
- <span hidden$="[[rebaseOnCurrent]]"> (not current branch)</span>
+ <div id="rebaseOnParentContainer" class="rebaseOption"
+ hidden$="[[!_displayParentOption(rebaseOnCurrent, hasParent)]]">
+ <input id="rebaseOnParent"
+ name="rebaseOptions"
+ type="radio"
+ on-tap="_handleRebaseOnParent">
+ <label id="rebaseOnParentLabel" for="rebaseOnParent">
+ Rebase on parent change
</label>
+ </div>
+ <div id="parentUpToDateMsg" class="message"
+ hidden$="[[!_displayParentUpToDateMsg(rebaseOnCurrent, hasParent)]]">
+ This change is up to date with its parent.
+ </div>
+ <div id="rebaseOnTip" class="rebaseOption"
+ hidden$="[[!_displayTipOption(rebaseOnCurrent, hasParent)]]">
+ <input id="rebaseOnTip"
+ name="rebaseOptions"
+ type="radio"
+ disabled$="[[!_displayTipOption(rebaseOnCurrent, hasParent)]]"
+ on-tap="_handleRebaseOnTip">
+ <label id="rebaseOnTipLabel" for="rebaseOnTip">
+ Rebase on top of the [[branch]] branch<span hidden="[[!hasParent]]">
+ (breaks relation chain)
+ </span>
+ </label>
+ </div>
+ <div id="tipUpToDateMsg" class="message"
+ hidden$="[[_displayTipOption(rebaseOnCurrent, hasParent)]]">
+ Change is up to date with the target branch already ([[branch]])
+ </div>
+ <div id="rebaseOnOther" class="rebaseOption">
+ <input id="rebaseOnOtherInput"
+ name="rebaseOptions"
+ type="radio"
+ on-tap="_handleRebaseOnOther">
+ <label id="rebaseOnOtherLabel" for="rebaseOnOtherInput">
+ Rebase on a specific change or ref <span hidden="[[!hasParent]]">
+ (breaks relation chain)
+ </span>
+ </label>
+ </div>
+ <div class="parentRevisionContainer">
<input is="iron-input"
type="text"
id="parentInput"
bind-value="{{base}}"
+ on-tap="_handleEnterChangeNumberTap"
placeholder="Change number">
</div>
- <div class="clearParentContainer">
- <input id="clearParent"
- hidden$="[[!rebaseOnCurrent]]"
- type="checkbox"
- on-tap="_handleClearParentTap"
- disabled="[[!rebaseOnCurrent]]">
- <label id="clearParentLabel" for="clearParent"
- hidden$="[[!rebaseOnCurrent]]">
- Rebase on top of current branch (clear parent revision).
- </label>
- <p id="rebaseUpToDateInfo" class="info" for="clearParent"
- hidden$="[[rebaseOnCurrent]]">
- Already up to date with current branch.
- </p>
- </div>
</div>
</gr-confirm-dialog>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
index 47fbee0..e1fbc09 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
@@ -33,15 +33,23 @@
base: String,
branch: String,
hasParent: Boolean,
- clearParent: {
- type: Boolean,
- value: false,
- },
rebaseOnCurrent: Boolean,
- valueSelected: {
- type: Boolean,
- computed: '_updateValueSelected(base, clearParent)',
- },
+ },
+
+ observers: [
+ '_updateSelectedOption(rebaseOnCurrent, hasParent)',
+ ],
+
+ _displayParentOption: function(rebaseOnCurrent, hasParent) {
+ return hasParent && rebaseOnCurrent;
+ },
+
+ _displayParentUpToDateMsg: function(rebaseOnCurrent, hasParent) {
+ return hasParent && !rebaseOnCurrent;
+ },
+
+ _displayTipOption: function(rebaseOnCurrent, hasParent) {
+ return !(!rebaseOnCurrent && !hasParent);
},
_handleConfirmTap: function(e) {
@@ -54,17 +62,44 @@
this.fire('cancel', null, {bubbles: false});
},
- _handleClearParentTap: function(e) {
- var clear = Polymer.dom(e).rootTarget.checked;
- if (clear) {
- this.base = '';
- }
- this.$.parentInput.disabled = clear;
- this.clearParent = clear;
+ _handleRebaseOnOther: function(e) {
+ this.$.parentInput.focus();
},
- _updateValueSelected: function(base, clearParent) {
- return base.length || clearParent;
+ /**
+ * There is a subtle but important difference between setting the base to an
+ * empty string and omitting it entirely from the payload. An empty string
+ * implies that the parent should be cleared and the change should be
+ * rebased on top of the target branch. Leaving out the base implies that it
+ * should be rebased on top of its current parent.
+ */
+ _handleRebaseOnTip: function(e) {
+ this.base = '';
+ },
+
+ _handleRebaseOnParent: function(e) {
+ this.base = null;
+ },
+
+ _handleEnterChangeNumberTap: function(e) {
+ this.$.rebaseOnOtherInput.checked = true;
+ },
+
+ /**
+ * Sets the default radio button based on the state of the app and
+ * the corresponding value to be submitted.
+ */
+ _updateSelectedOption: function(rebaseOnCurrent, hasParent) {
+ if (this._displayParentOption(rebaseOnCurrent, hasParent)) {
+ this.$.rebaseOnParent.checked = true;
+ this._handleRebaseOnParent();
+ } else if (this._displayTipOption(rebaseOnCurrent, hasParent)) {
+ this.$.rebaseOnTip.checked = true;
+ this._handleRebaseOnTip();
+ } else {
+ this.$.rebaseOnOtherInput.checked = true;
+ this._handleRebaseOnOther();
+ }
},
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
index d6e63d3..4f9bc96 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
@@ -38,47 +38,48 @@
element = fixture('basic');
});
- test('controls with rebase on current available', function() {
+ test('controls with parent and rebase on current available', function() {
element.rebaseOnCurrent = true;
+ element.hasParent = true;
flushAsynchronousOperations();
- // The correct content is hidden/displayed regarding the ability to rebase
- // on top of the current branch.
- assert.isFalse(element.$.clearParentLabel.hasAttribute('hidden'));
- assert.isTrue(element.$.rebaseUpToDateInfo.hasAttribute('hidden'));
- assert.isFalse(element.$.optionalText.hasAttribute('hidden'));
-
- assert.isFalse(!!element.valueSelected);
- assert.isFalse(element.$.parentInput.hasAttribute('disabled'));
- assert.isFalse(element.$.clearParent.hasAttribute('disabled'));
- assert.isFalse(element.$.clearParent.checked);
- element.base = 'something great';
- assert.isTrue(!!element.valueSelected);
- MockInteractions.tap(element.$.clearParent);
- assert.isTrue(!!element.valueSelected);
- assert.isTrue(element.$.parentInput.hasAttribute('disabled'));
- assert.isTrue(element.$.clearParent.checked);
- assert.equal(element.base, '');
- MockInteractions.tap(element.$.clearParent);
- assert.isFalse(!!element.valueSelected);
+ assert.isTrue(element.$.rebaseOnParent.checked);
+ assert.isFalse(element.$.rebaseOnParentContainer.hasAttribute('hidden'));
+ assert.isTrue(element.$.parentUpToDateMsg.hasAttribute('hidden'));
+ assert.isFalse(element.$.rebaseOnTip.hasAttribute('hidden'));
+ assert.isTrue(element.$.tipUpToDateMsg.hasAttribute('hidden'));
});
- test('controls without rebase on current available', function() {
+ test('controls with parent rebase on current not available', function() {
element.rebaseOnCurrent = false;
+ element.hasParent = true;
flushAsynchronousOperations();
- // The correct content is hidden/displayed regarding the ability to rebase
- // on top of the current branch.
- assert.isTrue(element.$.clearParentLabel.hasAttribute('hidden'));
- assert.isFalse(element.$.rebaseUpToDateInfo.hasAttribute('hidden'));
- assert.isTrue(element.$.optionalText.hasAttribute('hidden'));
+ assert.isTrue(element.$.rebaseOnTip.checked);
+ assert.isTrue(element.$.rebaseOnParentContainer.hasAttribute('hidden'));
+ assert.isFalse(element.$.parentUpToDateMsg.hasAttribute('hidden'));
+ assert.isFalse(element.$.rebaseOnTip.hasAttribute('hidden'));
+ assert.isTrue(element.$.tipUpToDateMsg.hasAttribute('hidden'));
+ });
- assert.isFalse(!!element.valueSelected);
- assert.isFalse(element.$.parentInput.hasAttribute('disabled'));
- assert.isTrue(element.$.clearParent.hasAttribute('disabled'));
- assert.isTrue(element.$.clearParentLabel.hasAttribute('hidden'));
- assert.isFalse(element.$.rebaseUpToDateInfo.hasAttribute('hidden'));
+ test('controls without parent and rebase on current available', function() {
+ element.rebaseOnCurrent = true;
+ element.hasParent = false;
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.rebaseOnTip.checked);
+ assert.isTrue(element.$.rebaseOnParentContainer.hasAttribute('hidden'));
+ assert.isTrue(element.$.parentUpToDateMsg.hasAttribute('hidden'));
+ assert.isFalse(element.$.rebaseOnTip.hasAttribute('hidden'));
+ assert.isTrue(element.$.tipUpToDateMsg.hasAttribute('hidden'));
+ });
- element.base = 'something great';
- assert.isTrue(!!element.valueSelected);
+ test('controls without parent rebase on current not available', function() {
+ element.rebaseOnCurrent = false;
+ element.hasParent = false;
+ flushAsynchronousOperations();
+ assert.isTrue(element.$.rebaseOnOtherInput.checked);
+ assert.isTrue(element.$.rebaseOnParentContainer.hasAttribute('hidden'));
+ assert.isTrue(element.$.parentUpToDateMsg.hasAttribute('hidden'));
+ assert.isTrue(element.$.rebaseOnTip.hasAttribute('hidden'));
+ assert.isFalse(element.$.tipUpToDateMsg.hasAttribute('hidden'));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
index 7c888da..bcd9053 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
@@ -49,8 +49,6 @@
input {
font-family: var(--monospace-font-family);
font-size: inherit;
- margin-bottom: .5em;
- width: 60em;
}
li[selected] gr-button {
color: #000;
@@ -71,6 +69,19 @@
justify-content: space-between;
padding-top: .75em;
}
+ .command {
+ display: flex;
+ flex-wrap: wrap;
+ margin-bottom: .5em;
+ width: 60em;
+ }
+ .command label {
+ flex: 0 0 100%;
+ }
+ .copyCommand {
+ flex-grow: 1;
+ margin-right: .3em;
+ }
.closeButtonContainer {
display: flex;
flex: 1;
@@ -112,10 +123,14 @@
<div class="command">
<label>[[command.title]]</label>
<input is="iron-input"
+ class="copyCommand"
type="text"
bind-value="[[command.command]]"
on-tap="_handleInputTap"
readonly>
+ <gr-button class="copyToClipboard" on-tap="_copyToClipboard">
+ copy
+ </gr-button>
</div>
</template>
</main>
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
index 72425a4..41f6792 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
@@ -51,7 +51,11 @@
],
focus: function() {
- this.$.download.focus();
+ if (this._schemes.length) {
+ this.$$('.copyToClipboard').focus();
+ } else {
+ this.$.download.focus();
+ }
},
getFocusStops: function() {
@@ -162,5 +166,13 @@
this._selectedScheme = schemes.sort()[0];
}
},
+
+ _copyToClipboard: function(e) {
+ e.target.parentElement.querySelector('.copyCommand').select();
+ document.execCommand('copy');
+ getSelection().removeAllRanges();
+ e.target.textContent = 'done';
+ setTimeout(function() { e.target.textContent = 'copy'; }, 1000);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
index 7d80c09..ce28d4e 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
@@ -97,6 +97,45 @@
};
}
+ function getChangeObjectNoFetch() {
+ return {
+ current_revision: '34685798fe548b6d17d1e8e5edc43a26d055cc72',
+ revisions: {
+ '34685798fe548b6d17d1e8e5edc43a26d055cc72': {
+ _number: 1,
+ fetch: {},
+ }
+ }
+ };
+ }
+
+ suite('gr-download-dialog tests with no fetch options', function() {
+ var element;
+
+ setup(function() {
+ element = fixture('basic');
+ element.change = getChangeObjectNoFetch();
+ element.patchNum = 1;
+ element.config = {
+ schemes: {
+ 'anonymous http': {},
+ http: {},
+ repo: {},
+ ssh: {},
+ },
+ archives: ['tgz', 'tar', 'tbz2', 'txz'],
+ };
+ });
+
+ test('focuses on first download link if no copy links', function() {
+ flushAsynchronousOperations();
+ var focusStub = sinon.stub(element.$.download, 'focus');
+ element.focus();
+ assert.isTrue(focusStub.called);
+ focusStub.restore();
+ });
+ });
+
suite('gr-download-dialog tests', function() {
var element;
@@ -115,14 +154,23 @@
};
});
- test('focuses on first download link', function() {
- var focusStub = sinon.stub(element.$.download, 'focus');
+ test('focuses on first copy link', function() {
+ flushAsynchronousOperations();
+ var focusStub = sinon.stub(element.$$('.copyToClipboard'), 'focus');
element.focus();
flushAsynchronousOperations();
assert.isTrue(focusStub.called);
focusStub.restore();
});
+ test('copy to clipboard', function() {
+ flushAsynchronousOperations();
+ var clipboardSpy = sinon.spy(element, '_copyToClipboard');
+ var copyBtn = element.$$('.copyToClipboard');
+ MockInteractions.tap(copyBtn);
+ assert.isTrue(clipboardSpy.called);
+ });
+
test('element visibility', function() {
assert.isFalse(element.$$('ul').hasAttribute('hidden'));
assert.isFalse(element.$$('main').hasAttribute('hidden'));
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index 7c1759d..05dfbcf 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -21,7 +21,8 @@
<dom-module id="gr-messages-list">
<template>
<style>
- :host {
+ :host,
+ .messageListControls {
display: block;
}
.header {
@@ -30,6 +31,7 @@
margin-bottom: .35em;
}
.header,
+ #messageControlsContainer,
gr-message {
padding: 0 var(--default-horizontal-margin);
}
@@ -40,10 +42,19 @@
0% { background-color: #fff9c4; }
100% { background-color: #fff; }
}
+ #messageControlsContainer {
+ align-items: center;
+ background-color: #fef;
+ display: flex;
+ justify-content: center;
+ }
+ #messageControlsContainer gr-button {
+ padding: 0.4em;
+ }
</style>
<div class="header">
<h3>Messages</h3>
- <div>
+ <div class="messageListControls">
<gr-button id="collapse-messages" link
on-tap="_handleExpandCollapseTap">
[[_computeExpandCollapseMessage(_expanded)]]
@@ -59,9 +70,21 @@
</span>
</div>
</div>
+ <span
+ id="messageControlsContainer"
+ hidden$="[[_computeShowHideTextHidden(_visibleMessages.length, _processedMessages, _hideAutomated)]]">
+ <gr-button id="oldMessagesBtn" link on-tap="_handleShowAllTap">
+ [[_computeNumMessagesText(_visibleMessages.length, _processedMessages, _hideAutomated)]]
+ </gr-button>
+ /
+ <gr-button id="incrementMessagesBtn" link
+ on-tap="_handleIncrementShownMessages">
+ [[_computeIncrementText(_visibleMessages.length, _processedMessages, _hideAutomated)]]
+ </gr-button>
+ </span>
<template
is="dom-repeat"
- items="[[_computeItems(messages, reviewerUpdates)]]"
+ items="[[_visibleMessages]]"
as="message">
<gr-message
change-num="[[changeNum]]"
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index fbbaadb..33dec56 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -14,6 +14,9 @@
(function() {
'use strict';
+ var MAX_INITIAL_SHOWN_MESSAGES = 5;
+ var MESSAGES_INCREMENT = 5;
+
Polymer({
is: 'gr-messages-list',
@@ -42,6 +45,21 @@
type: Boolean,
value: false,
},
+ /**
+ * The messages after processing and including merged reviewer updates.
+ */
+ _processedMessages: {
+ type: Array,
+ computed: '_computeItems(messages, reviewerUpdates)',
+ observer: '_processedMessagesChanged',
+ },
+ /**
+ * The subset of _processedMessages that is visible to the user.
+ */
+ _visibleMessages: {
+ type: Array,
+ value: function() { return []; },
+ },
},
scrollToMessage: function(messageID) {
@@ -59,6 +77,11 @@
this._highlightEl(el);
},
+ _isAutomated: function(message) {
+ return !!(message.reviewer ||
+ (message.tag && message.tag.indexOf('autogenerated') === 0));
+ },
+
_computeItems: function(messages, reviewerUpdates) {
messages = messages || [];
reviewerUpdates = reviewerUpdates || [];
@@ -70,6 +93,7 @@
for (var i = 0; i < messages.length; i++) {
messages[i]._index = i;
}
+
while (mi < messages.length || ri < reviewerUpdates.length) {
if (mi >= messages.length) {
result = result.concat(reviewerUpdates.slice(ri));
@@ -124,6 +148,7 @@
_handleAutomatedMessageToggleTap: function(e) {
e.preventDefault();
+
this._hideAutomated = !this._hideAutomated;
},
@@ -133,8 +158,7 @@
_hasAutomatedMessages: function(messages) {
for (var i = 0; messages && i < messages.length; i++) {
- if (messages[i].reviewer || (messages[i].tag &&
- messages[i].tag.indexOf('autogenerated') === 0)) {
+ if (this._isAutomated(messages[i])) {
return true;
}
}
@@ -196,5 +220,76 @@
}
return msgComments;
},
+
+ /**
+ * Returns the number of messages to splice to the beginning of
+ * _visibleMessages. This is the minimum of the total number of messages
+ * remaining in the list and the number of messages needed to display five
+ * more visible messages in the list.
+ */
+ _getDelta: function(numVisible, messages, hideAutomated) {
+ var delta = MESSAGES_INCREMENT;
+ var msgsRemaining = messages.length - numVisible;
+ if (hideAutomated) {
+ var counter = 0;
+ var i;
+ for (i = msgsRemaining; i > 0 && counter < MESSAGES_INCREMENT; i--) {
+ if (!this._isAutomated(messages[i - 1])) { counter++; }
+ }
+ delta = msgsRemaining - i;
+ }
+ return Math.min(msgsRemaining, delta);
+ },
+
+ /**
+ * Gets the number of messages that would be visible, but do not currently
+ * exist in _visibleMessages.
+ */
+ _numRemaining: function(numVisible, messages, hideAutomated) {
+ var total = hideAutomated ?
+ messages.filter(function(msg) {
+ return !this._isAutomated(msg);
+ }.bind(this)).length :
+ messages.length;
+ return total - numVisible;
+ },
+
+ _computeIncrementText: function(numVisible, messages, hideAutomated) {
+ var delta = this._getDelta(numVisible, messages, hideAutomated);
+ delta = Math.min(
+ this._numRemaining(numVisible, messages, hideAutomated), delta);
+ return 'Show ' + Math.min(MESSAGES_INCREMENT, delta) + ' more';
+ },
+
+ _computeShowHideTextHidden: function(numVisible, messages, hideAutomated) {
+ if (numVisible >= messages.length) { return true; }
+ if (!hideAutomated) {
+ return numVisible >= messages.length;
+ }
+ var hiddenMessages = messages.slice(0, messages.length - numVisible);
+ return this._hasAutomatedMessages(hiddenMessages);
+ },
+
+ _handleShowAllTap: function() {
+ this._visibleMessages = this._processedMessages;
+ },
+
+ _handleIncrementShownMessages: function() {
+ var len = this._visibleMessages.length;
+ var delta = this._getDelta(len, this._processedMessages,
+ this._hideAutomated);
+ var newMessages = this._processedMessages.slice(-(len + delta), -len);
+ // Add newMessages to the beginning of _visibleMessages
+ this.splice.apply(this, ['_visibleMessages', 0, 0].concat(newMessages));
+ },
+
+ _processedMessagesChanged: function(messages) {
+ this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
+ },
+
+ _computeNumMessagesText: function(numVisible, messages, hideAutomated) {
+ var total = this._numRemaining(numVisible, messages, hideAutomated);
+ return total === 1 ? 'Show 1 message' : 'Show all ' + total + ' messages';
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index d22cfd5..079e27c 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -31,24 +31,34 @@
</test-fixture>
<script>
+
+ var randomMessage = function(opt_params) {
+ var params = opt_params || {};
+ var author1 = {
+ _account_id: 1115495,
+ name: 'Andrew Bonventre',
+ email: 'andybons@chromium.org',
+ };
+ return {
+ id: params.id || Math.random().toString(),
+ date: params.date || '2016-01-12 20:28:33.038000',
+ message: params.message || Math.random().toString(),
+ _revision_number: params._revision_number || 1,
+ author: params.author || author1,
+ };
+ };
+
+ var randomAutomated = function(opt_params) {
+ return Object.assign({tag: 'autogenerated:gerrit:replace'},
+ randomMessage(opt_params));
+ };
+
suite('gr-messages-list tests', function() {
var element;
var messages;
- var randomMessage = function(opt_params) {
- var params = opt_params || {};
- var author1 = {
- _account_id: 1115495,
- name: 'Andrew Bonventre',
- email: 'andybons@chromium.org',
- };
- return {
- id: params.id || Math.random().toString(),
- date: params.date || '2016-01-12 20:28:33.038000',
- message: params.message || Math.random().toString(),
- _revision_number: params._revision_number || 1,
- author: params.author || author1,
- };
+ var getMessages = function() {
+ return Polymer.dom(element.root).querySelectorAll('gr-message');
};
setup(function() {
@@ -62,25 +72,72 @@
flushAsynchronousOperations();
});
+ test('show some old messages', function() {
+ assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
+ element.messages = _.times(11, randomMessage);
+ flushAsynchronousOperations();
+
+ assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
+ assert.equal(getMessages().length, 5);
+ assert.equal(element.$.incrementMessagesBtn.innerText,
+ 'Show 5 more');
+ MockInteractions.tap(element.$.incrementMessagesBtn);
+ flushAsynchronousOperations();
+
+ assert.equal(getMessages().length, 10);
+ assert.equal(element.$.incrementMessagesBtn.innerText,
+ 'Show 1 more');
+ MockInteractions.tap(element.$.incrementMessagesBtn);
+ flushAsynchronousOperations();
+
+ assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
+ assert.equal(getMessages().length, 11);
+ });
+
+ test('show all old messages', function() {
+ assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
+ element.messages = _.times(11, randomMessage);
+ flushAsynchronousOperations();
+
+ assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
+ assert.equal(getMessages().length, 5);
+ assert.equal(element.$.oldMessagesBtn.innerText, 'Show all 6 messages');
+ MockInteractions.tap(element.$.oldMessagesBtn);
+ flushAsynchronousOperations();
+
+ assert.equal(getMessages().length, 11);
+ assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
+ });
+
+ test('message count respects automated', function() {
+ element.messages = _.times(3, randomAutomated)
+ .concat(_.times(3, randomMessage));
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.oldMessagesBtn.innerText, 'Show 1 message');
+ assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
+ MockInteractions.tap(element.$.automatedMessageToggle);
+ flushAsynchronousOperations();
+
+ assert.isTrue(element.$.messageControlsContainer.hasAttribute('hidden'));
+ });
+
test('expand/collapse all', function() {
- var allMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message');
+ var allMessageEls = getMessages();
for (var i = 0; i < allMessageEls.length; i++) {
allMessageEls[i].expanded = false;
}
MockInteractions.tap(allMessageEls[1]);
assert.isTrue(allMessageEls[1].expanded);
- MockInteractions.tap(element.$$('.header gr-button'));
- allMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message');
+ MockInteractions.tap(element.$$('#collapse-messages'));
+ allMessageEls = getMessages();
for (var i = 0; i < allMessageEls.length; i++) {
assert.isTrue(allMessageEls[i].expanded);
}
MockInteractions.tap(element.$$('#collapse-messages'));
- allMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message');
+ allMessageEls = getMessages();
for (var i = 0; i < allMessageEls.length; i++) {
assert.isFalse(allMessageEls[i].expanded);
}
@@ -88,25 +145,23 @@
test('expand/collapse from external keypress', function() {
element.handleExpandCollapse(true);
- var allMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message');
+ var allMessageEls = getMessages();
for (var i = 0; i < allMessageEls.length; i++) {
assert.isTrue(allMessageEls[i].expanded);
}
// Expand/collapse all text also changes.
assert.equal(element.$$('#collapse-messages').textContent.trim(),
- 'Collapse all');
+ 'Collapse all');
element.handleExpandCollapse(false);
- var allMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message');
+ allMessageEls = getMessages();
for (var i = 0; i < allMessageEls.length; i++) {
assert.isFalse(allMessageEls[i].expanded);
}
// Expand/collapse all text also changes.
assert.equal(element.$$('#collapse-messages').textContent.trim(),
- 'Expand all');
+ 'Expand all');
});
test('hide messages does not appear when no automated messages',
@@ -115,8 +170,7 @@
});
test('scroll to message', function() {
- var allMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message');
+ var allMessageEls = getMessages();
for (var i = 0; i < allMessageEls.length; i++) {
allMessageEls[i].expanded = false;
}
@@ -192,7 +246,7 @@
patch_set: 2,
author: author,
},
- ]
+ ],
};
var messages = [].concat(
randomMessage(),
@@ -220,8 +274,7 @@
};
var isMarvin = isAuthor.bind(null, author);
flushAsynchronousOperations();
- var messageElements =
- Polymer.dom(element.root).querySelectorAll('gr-message');
+ var messageElements = getMessages();
assert.equal(messageElements.length, messages.length);
assert.deepEqual(messageElements[1].message, messages[1]);
assert.deepEqual(messageElements[2].message, messages[2]);
@@ -258,7 +311,7 @@
element.messages = messages;
element.comments = comments;
flushAsynchronousOperations();
- var messageEls = Polymer.dom(element.root).querySelectorAll('gr-message');
+ var messageEls = getMessages();
assert.equal(messageEls.length, 1);
assert.equal(messageEls[0].message.message, messages[0].message);
});
@@ -268,21 +321,11 @@
var element;
var messages;
- var randomMessage = function(opt_params) {
- var params = opt_params || {};
- var author1 = {
- _account_id: 1115495,
- name: 'Andrew Bonventre',
- email: 'andybons@chromium.org',
- };
- return {
- id: params.id || Math.random().toString(),
- date: params.date || '2016-01-12 20:28:33.038000',
- message: params.message || Math.random().toString(),
- _revision_number: params._revision_number || 1,
- author: params.author || author1,
- tag: 'autogenerated:gerrit:replace',
- };
+ var getMessages = function() {
+ return Polymer.dom(element.root).querySelectorAll('gr-message');
+ };
+ var getHiddenMessages = function() {
+ return Polymer.dom(element.root).querySelectorAll('gr-message[hidden]');
};
var randomMessageReviewer = {
@@ -295,7 +338,7 @@
getLoggedIn: function() { return Promise.resolve(false); },
});
element = fixture('basic');
- messages = _.times(2, randomMessage);
+ messages = _.times(2, randomAutomated);
messages.push(randomMessageReviewer);
element.messages = messages;
flushAsynchronousOperations();
@@ -306,42 +349,51 @@
});
test('autogenerated messages are not hidden initially', function() {
- var allHiddenMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message[hidden]');
+ var allHiddenMessageEls = getHiddenMessages();
//There are no hidden messages.
assert.isFalse(!!allHiddenMessageEls.length);
});
- test('autogenerated messages are hidden after clicking hide button',
- function() {
- var allHiddenMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message[hidden]');
+ test('autogenerated messages hidden after hide button tap', function() {
+ var allHiddenMessageEls = getHiddenMessages();
element._hideAutomated = false;
- MockInteractions.tap(element.$$('#automatedMessageToggle'));
- allMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message');
- allHiddenMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message[hidden]');
+ MockInteractions.tap(element.$.automatedMessageToggle);
+ flushAsynchronousOperations();
+ allMessageEls = getMessages();
+ allHiddenMessageEls = getHiddenMessages();
// Autogenerated messages are now hidden.
assert.equal(allHiddenMessageEls.length, allMessageEls.length);
});
- test('autogenerated messages are not hidden after clicking show button',
- function() {
- var allHiddenMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message[hidden]');
+ test('autogenerated messages not hidden after show button tap', function() {
+ var allHiddenMessageEls = getHiddenMessages();
element._hideAutomated = true;
- MockInteractions.tap(element.$$('#automatedMessageToggle'));
- allHiddenMessageEls =
- Polymer.dom(element.root).querySelectorAll('gr-message[hidden]');
+ MockInteractions.tap(element.$.automatedMessageToggle);
+ allHiddenMessageEls = getHiddenMessages();
//Autogenerated messages are now hidden.
assert.isFalse(!!allHiddenMessageEls.length);
});
-});
+ test('_getDelta', function() {
+ var messages = [randomMessage()];
+ assert.equal(element._getDelta(0, messages, false), 1);
+ assert.equal(element._getDelta(0, messages, true), 1);
+
+
+ messages = _.times(7, randomMessage);
+ assert.equal(element._getDelta(0, messages, false), 5);
+ assert.equal(element._getDelta(0, messages, true), 5);
+
+ messages = _.times(4, randomMessage)
+ .concat(_.times(2, randomAutomated))
+ .concat(_.times(3, randomMessage));
+ assert.equal(element._getDelta(2, messages, false), 5);
+ assert.equal(element._getDelta(2, messages, true), 7);
+ });
+ });
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index d62dbf8..54a72f6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -480,20 +480,38 @@
return index + 1;
};
+ GrDiffBuilder.prototype._advancePastTagClose = function(html, index) {
+ while (index < html.length &&
+ html.charCodeAt(index) !== GrDiffBuilder.GREATER_THAN_CODE) {
+ index++;
+ }
+ return index + 1;
+ };
+
GrDiffBuilder.prototype._addNewlines = function(text, html) {
var htmlIndex = 0;
var indices = [];
var numChars = 0;
+ var prevHtmlIndex = 0;
for (var i = 0; i < text.length; i++) {
if (numChars > 0 && numChars % this._prefs.line_length === 0) {
indices.push(htmlIndex);
}
htmlIndex = this._advanceChar(html, htmlIndex);
if (text[i] === '\t') {
+ // Advance past tab closing tag.
+ htmlIndex = this._advancePastTagClose(html, htmlIndex);
+ // ~~ is a faster Math.floor
+ if (~~(numChars / this._prefs.line_length) !==
+ ~~((numChars + this._prefs.tab_size) / this._prefs.line_length)) {
+ // Tab crosses line limit - push it to the next line.
+ indices.push(prevHtmlIndex);
+ }
numChars += this._prefs.tab_size;
} else {
numChars++;
}
+ prevHtmlIndex = htmlIndex;
}
var result = html;
// Since the result string is being altered in place, start from the end
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index e5fe1d7..88ce477 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -97,7 +97,7 @@
assert.equal(buttons[2].textContent, '+10↓');
});
- test('newlines', function() {
+ test('newlines 1', function() {
var text = 'abcdef';
assert.equal(builder._addNewlines(text, text), text);
text = 'a'.repeat(20);
@@ -105,8 +105,10 @@
'a'.repeat(10) +
GrDiffBuilder.LINE_FEED_HTML +
'a'.repeat(10));
+ });
- text = '<span class="thumbsup">👍</span>';
+ test('newlines 2', function() {
+ var text = '<span class="thumbsup">👍</span>';
var html = '<span class="thumbsup">👍</span>';
assert.equal(builder._addNewlines(text, html),
'<span clas' +
@@ -116,10 +118,13 @@
'p">👍</spa' +
GrDiffBuilder.LINE_FEED_HTML +
'n>');
+ });
- text = '01234\t56789';
- assert.equal(builder._addNewlines(text, text),
- '01234\t5' +
+ test('newlines 3', function() {
+ var text = '01234\t56789';
+ var html = '01234<span>\t</span>56789';
+ assert.equal(builder._addNewlines(text, html),
+ '01234<span>\t</span>5' +
GrDiffBuilder.LINE_FEED_HTML +
'6789');
});
@@ -154,6 +159,17 @@
});
});
+ test('_createTextEl linewrap with tabs', function() {
+ var text = _.times(7, _.constant('\t')).join('') + '!';
+ var line = {text: text, highlights: []};
+ var el = builder._createTextEl(line);
+ var tabEl = el.querySelector('.contentText > .br');
+ assert.isOk(tabEl);
+ assert.equal(
+ el.querySelector('.contentText .tab:nth-child(2)').nextSibling,
+ tabEl);
+ });
+
test('text length with tabs and unicode', function() {
assert.equal(builder._textLength('12345', 4), 5);
assert.equal(builder._textLength('\t\t12', 4), 10);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
index 4ca7f28..cb9a7b8 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -50,6 +50,7 @@
setup(function() {
element = fixture('basic');
element.change = {};
+ element._hasKnownChainState = false;
var plugin;
Gerrit.install(function(p) { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
@@ -110,7 +111,7 @@
var button = element.$$('[data-action-key="' + key + '"]');
assert.isOk(button);
assert.equal(button.getAttribute('data-label'), 'Bork!');
- assert.isFalse(button.disabled);
+ assert.isNotOk(button.disabled);
changeActions.setLabel(key, 'Yo');
changeActions.setEnabled(key, false);
flush(function() {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index b9b1f53..b54840e 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -693,7 +693,7 @@
return fetch(url, options).then(function(response) {
if (!response.ok) {
if (opt_errFn) {
- opt_errFn.call(null, response);
+ opt_errFn.call(opt_ctx || null, response);
return undefined;
}
this.fire('server-error', {response: response});