Modify voting and submit requirements UI
Bug: Issue 8882 Change-Id: If6e8e82327ecf9bf14122c1a583884760aa6f5e1
This commit is contained in:
parent
8f420ae258
commit
b33b2155b0
@ -98,10 +98,10 @@ limitations under the License.
|
||||
font-family: var(--monospace-font-family);
|
||||
}
|
||||
.u-green {
|
||||
color: #388E3C;
|
||||
color: var(--vote-text-color-recommended);
|
||||
}
|
||||
.u-red {
|
||||
color: #D32F2F;
|
||||
color: var(--vote-text-color-disliked);
|
||||
}
|
||||
.label.u-green:not(.u-monospace),
|
||||
.label.u-red:not(.u-monospace) {
|
||||
|
@ -145,7 +145,6 @@ limitations under the License.
|
||||
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
|
||||
Gerrit._setPluginsPending([]);
|
||||
element = createElement();
|
||||
sandbox.stub(element, '_computeCanDeleteVote').returns(true);
|
||||
|
||||
labelChangeStub = sandbox.stub();
|
||||
plugin.changeMetadata().onLabelsChanged(labelChangeStub);
|
||||
@ -156,8 +155,18 @@ limitations under the License.
|
||||
assert.equal(labelChangeStub.callCount, 1);
|
||||
assert.isTrue(labelChangeStub.calledWithExactly(labels));
|
||||
assert.equal(labelChangeStub.args[0][0]['CI'].all.length, 2);
|
||||
MockInteractions.tap(Polymer.dom(element.root).querySelector(
|
||||
'gr-account-chip').$.remove);
|
||||
element.set(['change', 'labels'], {
|
||||
CI: {
|
||||
all: [
|
||||
{value: 1, name: 'user 2', _account_id: 1},
|
||||
],
|
||||
values: {
|
||||
' 0': 'Don\'t submit as-is',
|
||||
'+1': 'No score',
|
||||
'+2': 'Looks good to me',
|
||||
},
|
||||
},
|
||||
});
|
||||
// Wait for fake rest API response.
|
||||
flush(() => {
|
||||
assert.equal(labelChangeStub.callCount, 2);
|
||||
|
@ -27,7 +27,6 @@ limitations under the License.
|
||||
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
|
||||
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
|
||||
<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
|
||||
<link rel="import" href="../../shared/gr-label/gr-label.html">
|
||||
<link rel="import" href="../../shared/gr-limited-text/gr-limited-text.html">
|
||||
<link rel="import" href="../../shared/gr-linked-chip/gr-linked-chip.html">
|
||||
<link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
|
||||
@ -38,7 +37,6 @@ limitations under the License.
|
||||
|
||||
<dom-module id="gr-change-metadata">
|
||||
<template>
|
||||
<style include="gr-voting-styles"></style>
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: table;
|
||||
@ -61,9 +59,16 @@ limitations under the License.
|
||||
color: var(--deemphasized-text-color);
|
||||
font-family: var(--font-family-bold);
|
||||
max-width: 20em;
|
||||
padding-left: var(--metadata-horizontal-padding);
|
||||
padding-right: .5em;
|
||||
word-break: break-word;
|
||||
}
|
||||
.value {
|
||||
padding-right: var(--metadata-horizontal-padding);
|
||||
}
|
||||
gr-change-requirements {
|
||||
--requirements-horizontal-padding: var(--metadata-horizontal-padding);
|
||||
}
|
||||
gr-account-link {
|
||||
max-width: 20ch;
|
||||
overflow: hidden;
|
||||
@ -74,35 +79,6 @@ limitations under the License.
|
||||
gr-editable-label {
|
||||
max-width: 9em;
|
||||
}
|
||||
.labelValueContainer:not(:first-of-type) {
|
||||
margin-top: .25em;
|
||||
}
|
||||
.labelValueContainer span {
|
||||
align-items: baseline;
|
||||
display: inline-flex;
|
||||
}
|
||||
.labelValueContainer {
|
||||
border-radius: 3px;
|
||||
padding: .1em .3em;
|
||||
}
|
||||
gr-label {
|
||||
margin-right: .3em;
|
||||
padding: .05em .85em;
|
||||
text-align: center;
|
||||
@apply --vote-chip-styles;
|
||||
}
|
||||
.max {
|
||||
background-color: var(--vote-color-approved);
|
||||
}
|
||||
.min {
|
||||
background-color: var(--vote-color-rejected);
|
||||
}
|
||||
.positive {
|
||||
background-color: var(--vote-color-recommended);
|
||||
}
|
||||
.negative {
|
||||
background-color: var(--vote-color-disliked);
|
||||
}
|
||||
.webLink {
|
||||
display: block;
|
||||
}
|
||||
@ -142,6 +118,11 @@ limitations under the License.
|
||||
--arrow-color: #ffa62f;
|
||||
display: inline-block;
|
||||
}
|
||||
.separatedSection {
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-top: .5em;
|
||||
padding: .5em 0;
|
||||
}
|
||||
</style>
|
||||
<gr-external-style id="externalStyle" name="change-metadata">
|
||||
<section>
|
||||
@ -174,7 +155,7 @@ limitations under the License.
|
||||
placeholder="Set assignee..."
|
||||
accounts="{{_assignee}}"
|
||||
change="[[change]]"
|
||||
readonly="[[_computeAssigneeReadOnly(mutable, change)]]"
|
||||
readonly="[[_computeAssigneeReadOnly(_mutable, change)]]"
|
||||
allow-any-user></gr-account-list>
|
||||
</span>
|
||||
</section>
|
||||
@ -184,7 +165,7 @@ limitations under the License.
|
||||
<span class="value">
|
||||
<gr-reviewer-list
|
||||
change="{{change}}"
|
||||
mutable="[[mutable]]"
|
||||
mutable="[[_mutable]]"
|
||||
reviewers-only
|
||||
max-reviewers-displayed="3"></gr-reviewer-list>
|
||||
</span>
|
||||
@ -194,7 +175,7 @@ limitations under the License.
|
||||
<span class="value">
|
||||
<gr-reviewer-list
|
||||
change="{{change}}"
|
||||
mutable="[[mutable]]"
|
||||
mutable="[[_mutable]]"
|
||||
ccs-only
|
||||
max-reviewers-displayed="3"></gr-reviewer-list>
|
||||
</span>
|
||||
@ -206,7 +187,7 @@ limitations under the License.
|
||||
<span class="value">
|
||||
<gr-reviewer-list
|
||||
change="{{change}}"
|
||||
mutable="[[mutable]]"></gr-reviewer-list>
|
||||
mutable="[[_mutable]]"></gr-reviewer-list>
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
@ -289,53 +270,24 @@ limitations under the License.
|
||||
on-remove="_handleHashtagRemoved">
|
||||
</gr-linked-chip>
|
||||
</template>
|
||||
<gr-editable-label
|
||||
uppercase
|
||||
label-text="Add a hashtag"
|
||||
value="{{_newHashtag}}"
|
||||
placeholder="[[_computeHashtagPlaceholder(_hashtagReadOnly)]]"
|
||||
read-only="[[_hashtagReadOnly]]"
|
||||
on-changed="_handleHashtagChanged"></gr-editable-label>
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
<template is="dom-repeat"
|
||||
items="[[_computeLabelNames(labels)]]" as="labelName">
|
||||
<section>
|
||||
<span class="title">[[labelName]]</span>
|
||||
<span class="value">
|
||||
<template is="dom-repeat"
|
||||
items="[[_computeLabelValues(labelName, labels.*)]]"
|
||||
as="label">
|
||||
<div class="labelValueContainer">
|
||||
<span>
|
||||
<gr-label
|
||||
has-tooltip
|
||||
title="[[_computeValueTooltip(change, label.value, labelName)]]"
|
||||
class$="[[label.className]] voteChip">
|
||||
[[label.value]]
|
||||
</gr-label>
|
||||
<gr-account-chip
|
||||
account="[[label.account]]"
|
||||
data-account-id$="[[label.account._account_id]]"
|
||||
label-name="[[labelName]]"
|
||||
removable="[[_computeCanDeleteVote(label.account, mutable)]]"
|
||||
transparent-background
|
||||
on-remove="_onDeleteVote"></gr-account-chip>
|
||||
</span>
|
||||
</div>
|
||||
<template is="dom-if" if="[[!_hashtagReadOnly]]">
|
||||
<gr-editable-label
|
||||
uppercase
|
||||
label-text="Add a hashtag"
|
||||
value="{{_newHashtag}}"
|
||||
placeholder="[[_computeHashtagPlaceholder(_hashtagReadOnly)]]"
|
||||
read-only="[[_hashtagReadOnly]]"
|
||||
on-changed="_handleHashtagChanged"></gr-editable-label>
|
||||
</template>
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showRequirements]]">
|
||||
<section class="requirementsStatus">
|
||||
<span class="title">Submit Status</span>
|
||||
<span class="value">
|
||||
<gr-change-requirements change="[[change]]"></gr-change-requirements>
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
<div class="separatedSection">
|
||||
<gr-change-requirements
|
||||
change="{{change}}"
|
||||
account="[[account]]"
|
||||
mutable="[[_mutable]]"></gr-change-requirements>
|
||||
</div>
|
||||
<section id="webLinks" hidden$="[[!_computeWebLinks(commitInfo)]]">
|
||||
<span class="title">Links</span>
|
||||
<span class="value">
|
||||
|
@ -46,10 +46,14 @@
|
||||
type: Object,
|
||||
notify: true,
|
||||
},
|
||||
account: Object,
|
||||
/** @type {?} */
|
||||
revision: Object,
|
||||
commitInfo: Object,
|
||||
mutable: Boolean,
|
||||
_mutable: {
|
||||
type: Boolean,
|
||||
computed: '_computeIsMutable(account)',
|
||||
},
|
||||
/**
|
||||
* @type {{ note_db_enabled: string }}
|
||||
*/
|
||||
@ -62,11 +66,11 @@
|
||||
},
|
||||
_topicReadOnly: {
|
||||
type: Boolean,
|
||||
computed: '_computeTopicReadOnly(mutable, change)',
|
||||
computed: '_computeTopicReadOnly(_mutable, change)',
|
||||
},
|
||||
_hashtagReadOnly: {
|
||||
type: Boolean,
|
||||
computed: '_computeHashtagReadOnly(mutable, change)',
|
||||
computed: '_computeHashtagReadOnly(_mutable, change)',
|
||||
},
|
||||
_showReviewersByState: {
|
||||
type: Boolean,
|
||||
@ -156,60 +160,6 @@
|
||||
return Object.keys(labels).sort();
|
||||
},
|
||||
|
||||
_computeLabelValues(labelName, _labels) {
|
||||
const result = [];
|
||||
const labels = _labels.base;
|
||||
const labelInfo = labels[labelName];
|
||||
if (!labelInfo) { return result; }
|
||||
if (!labelInfo.values) {
|
||||
if (labelInfo.rejected || labelInfo.approved) {
|
||||
const ok = labelInfo.approved || !labelInfo.rejected;
|
||||
return [{
|
||||
value: ok ? '👍️' : '👎️',
|
||||
className: ok ? 'positive' : 'negative',
|
||||
account: ok ? labelInfo.approved : labelInfo.rejected,
|
||||
}];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const approvals = labelInfo.all || [];
|
||||
const values = Object.keys(labelInfo.values);
|
||||
for (const label of approvals) {
|
||||
if (label.value && label.value != labels[labelName].default_value) {
|
||||
let labelClassName;
|
||||
let labelValPrefix = '';
|
||||
if (label.value > 0) {
|
||||
labelValPrefix = '+';
|
||||
if (parseInt(label.value, 10) ===
|
||||
parseInt(values[values.length - 1], 10)) {
|
||||
labelClassName = 'max';
|
||||
} else {
|
||||
labelClassName = 'positive';
|
||||
}
|
||||
} else if (label.value < 0) {
|
||||
if (parseInt(label.value, 10) === parseInt(values[0], 10)) {
|
||||
labelClassName = 'min';
|
||||
} else {
|
||||
labelClassName = 'negative';
|
||||
}
|
||||
}
|
||||
result.push({
|
||||
value: labelValPrefix + label.value,
|
||||
className: labelClassName,
|
||||
account: label,
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
_computeValueTooltip(change, score, labelName) {
|
||||
if (!change.labels[labelName] ||
|
||||
!change.labels[labelName].values ||
|
||||
!change.labels[labelName].values[score]) { return ''; }
|
||||
return change.labels[labelName].values[score];
|
||||
},
|
||||
|
||||
_handleTopicChanged(e, topic) {
|
||||
const lastTopic = this.change.topic;
|
||||
if (!topic.length) { topic = null; }
|
||||
@ -298,75 +248,6 @@
|
||||
return hasRequirements || hasLabels || !!change.work_in_progress;
|
||||
},
|
||||
|
||||
/**
|
||||
* A user is able to delete a vote iff the mutable property is true and the
|
||||
* reviewer that left the vote exists in the list of removable_reviewers
|
||||
* received from the backend.
|
||||
*
|
||||
* @param {!Object} reviewer An object describing the reviewer that left the
|
||||
* vote.
|
||||
* @param {boolean} mutable this.mutable describes whether the
|
||||
* change-metadata section is modifiable by the current user.
|
||||
*/
|
||||
_computeCanDeleteVote(reviewer, mutable) {
|
||||
if (!mutable || !this.change || !this.change.removable_reviewers) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < this.change.removable_reviewers.length; i++) {
|
||||
if (this.change.removable_reviewers[i]._account_id ===
|
||||
reviewer._account_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closure annotation for Polymer.prototype.splice is off.
|
||||
* For now, supressing annotations.
|
||||
*
|
||||
* TODO(beckysiegel) submit Polymer PR
|
||||
*
|
||||
* @suppress {checkTypes} */
|
||||
_onDeleteVote(e) {
|
||||
e.preventDefault();
|
||||
const target = Polymer.dom(e).rootTarget;
|
||||
target.disabled = true;
|
||||
const labelName = target.labelName;
|
||||
const accountID = parseInt(target.getAttribute('data-account-id'), 10);
|
||||
this._xhrPromise =
|
||||
this.$.restAPI.deleteVote(this.change._number, accountID, labelName)
|
||||
.then(response => {
|
||||
target.disabled = false;
|
||||
if (!response.ok) { return response; }
|
||||
const label = this.change.labels[labelName];
|
||||
const labels = label.all || [];
|
||||
let wasChanged = false;
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
if (labels[i]._account_id === accountID) {
|
||||
for (const key in label) {
|
||||
if (label.hasOwnProperty(key) &&
|
||||
label[key]._account_id === accountID) {
|
||||
// Remove special label field, keeping change label values
|
||||
// in sync with the backend.
|
||||
this.change.labels[labelName][key] = null;
|
||||
wasChanged = true;
|
||||
}
|
||||
}
|
||||
this.change.labels[labelName].all.splice(i, 1);
|
||||
wasChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (wasChanged) {
|
||||
this.notifyPath('change.labels');
|
||||
}
|
||||
}).catch(err => {
|
||||
target.disabled = false;
|
||||
return;
|
||||
});
|
||||
},
|
||||
|
||||
_computeProjectURL(project) {
|
||||
return Gerrit.Nav.getUrlForProjectChanges(project);
|
||||
},
|
||||
@ -458,5 +339,9 @@
|
||||
parentIsCurrent ? 'current' : 'notCurrent',
|
||||
].join(' ');
|
||||
},
|
||||
|
||||
_computeIsMutable(account) {
|
||||
return !!Object.keys(account).length;
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
@ -296,32 +296,6 @@ limitations under the License.
|
||||
element._computeShowUploaderHide(change), 'hideDisplay');
|
||||
});
|
||||
|
||||
test('_computeValueTooltip', () => {
|
||||
// Existing label.
|
||||
const change = {labels: {'Foo-bar': {values: {0: 'Baz'}}}};
|
||||
let score = '0';
|
||||
let labelName = 'Foo-bar';
|
||||
let actual = element._computeValueTooltip(change, score, labelName);
|
||||
assert.equal(actual, 'Baz');
|
||||
|
||||
// Non-extsistent label.
|
||||
labelName = 'xyz';
|
||||
actual = element._computeValueTooltip(change, score, labelName);
|
||||
assert.equal(actual, '');
|
||||
|
||||
// Non-extsistent score.
|
||||
score = '2';
|
||||
actual = element._computeValueTooltip(change, score, labelName);
|
||||
assert.equal(actual, '');
|
||||
|
||||
// No values on label.
|
||||
labelName = 'abcd';
|
||||
score = '0';
|
||||
change.labels.abcd = {};
|
||||
actual = element._computeValueTooltip(change, score, labelName);
|
||||
assert.equal(actual, '');
|
||||
});
|
||||
|
||||
test('_computeParents', () => {
|
||||
const parents = [{commit: '123', subject: 'abc'}];
|
||||
assert.isUndefined(element._computeParents(
|
||||
@ -403,7 +377,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('topic read only hides delete button', () => {
|
||||
element.mutable = false;
|
||||
element.account = {};
|
||||
element.change = change;
|
||||
flushAsynchronousOperations();
|
||||
const button = element.$$('gr-linked-chip').$$('gr-button');
|
||||
@ -411,7 +385,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('topic not read only does not hide delete button', () => {
|
||||
element.mutable = true;
|
||||
element.account = {test: true};
|
||||
change.actions.topic.enabled = true;
|
||||
element.change = change;
|
||||
flushAsynchronousOperations();
|
||||
@ -463,7 +437,7 @@ limitations under the License.
|
||||
note_db_enabled: true,
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
element.mutable = false;
|
||||
element.account = {};
|
||||
element.change = change;
|
||||
flushAsynchronousOperations();
|
||||
const button = element.$$('gr-linked-chip').$$('gr-button');
|
||||
@ -475,7 +449,7 @@ limitations under the License.
|
||||
note_db_enabled: true,
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
element.mutable = true;
|
||||
element.account = {test: true};
|
||||
change.actions.hashtags.enabled = true;
|
||||
element.change = change;
|
||||
flushAsynchronousOperations();
|
||||
@ -486,7 +460,6 @@ limitations under the License.
|
||||
|
||||
suite('remove reviewer votes', () => {
|
||||
setup(() => {
|
||||
sandbox.stub(element, '_computeValueTooltip').returns('');
|
||||
sandbox.stub(element, '_computeTopicReadOnly').returns(true);
|
||||
element.change = {
|
||||
_number: 42,
|
||||
@ -507,44 +480,56 @@ limitations under the License.
|
||||
flushAsynchronousOperations();
|
||||
});
|
||||
|
||||
test('_computeCanDeleteVote hides delete button', () => {
|
||||
const button = element.$$('gr-account-chip').$$('gr-button');
|
||||
assert.isTrue(button.hasAttribute('hidden'));
|
||||
element.mutable = true;
|
||||
assert.isTrue(button.hasAttribute('hidden'));
|
||||
});
|
||||
|
||||
test('_computeCanDeleteVote shows delete button', () => {
|
||||
element.change.removable_reviewers = [
|
||||
{
|
||||
_account_id: 1,
|
||||
name: 'bojack',
|
||||
},
|
||||
];
|
||||
element.mutable = true;
|
||||
const button = element.$$('gr-account-chip').$$('gr-button');
|
||||
assert.isFalse(button.hasAttribute('hidden'));
|
||||
});
|
||||
|
||||
test('deletes votes', () => {
|
||||
const deleteResponse = Promise.resolve({ok: true});
|
||||
const deleteStub = sandbox.stub(
|
||||
element.$.restAPI, 'deleteVote').returns(deleteResponse);
|
||||
|
||||
element.change.removable_reviewers = [{
|
||||
suite('assignee field', () => {
|
||||
const dummyAccount = {
|
||||
_account_id: 1,
|
||||
name: 'bojack',
|
||||
}];
|
||||
element.change.labels.test.recommended = {_account_id: 1};
|
||||
element.mutable = true;
|
||||
const chip = element.$$('gr-account-chip');
|
||||
const button = chip.$$('gr-button');
|
||||
MockInteractions.tap(button);
|
||||
assert.isTrue(chip.disabled);
|
||||
return deleteResponse.then(() => {
|
||||
assert.isFalse(chip.disabled);
|
||||
assert.notOk(element.change.labels.test.recommended);
|
||||
assert.isTrue(deleteStub.calledWithExactly(42, 1, 'test'));
|
||||
};
|
||||
const change = {
|
||||
actions: {
|
||||
assignee: {enabled: false},
|
||||
},
|
||||
assignee: dummyAccount,
|
||||
};
|
||||
let deleteStub;
|
||||
let setStub;
|
||||
|
||||
setup(() => {
|
||||
deleteStub = sandbox.stub(element.$.restAPI, 'deleteAssignee');
|
||||
setStub = sandbox.stub(element.$.restAPI, 'setAssignee');
|
||||
});
|
||||
|
||||
test('changing change recomputes _assignee', () => {
|
||||
assert.isFalse(!!element._assignee.length);
|
||||
const change = element.change;
|
||||
change.assignee = dummyAccount;
|
||||
element._changeChanged(change);
|
||||
assert.deepEqual(element._assignee[0], dummyAccount);
|
||||
});
|
||||
|
||||
test('modifying _assignee calls API', () => {
|
||||
assert.isFalse(!!element._assignee.length);
|
||||
element.set('_assignee', [dummyAccount]);
|
||||
assert.isTrue(setStub.calledOnce);
|
||||
assert.deepEqual(element.change.assignee, dummyAccount);
|
||||
element.set('_assignee', [dummyAccount]);
|
||||
assert.isTrue(setStub.calledOnce);
|
||||
element.set('_assignee', []);
|
||||
assert.isTrue(deleteStub.calledOnce);
|
||||
assert.equal(element.change.assignee, undefined);
|
||||
element.set('_assignee', []);
|
||||
assert.isTrue(deleteStub.calledOnce);
|
||||
});
|
||||
|
||||
test('_computeAssigneeReadOnly', () => {
|
||||
let mutable = false;
|
||||
assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
|
||||
mutable = true;
|
||||
assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
|
||||
change.actions.assignee.enabled = true;
|
||||
assert.isFalse(element._computeAssigneeReadOnly(mutable, change));
|
||||
mutable = false;
|
||||
assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
|
||||
});
|
||||
});
|
||||
|
||||
@ -600,59 +585,6 @@ limitations under the License.
|
||||
assert.equal(element.change.hashtags, newHashtag);
|
||||
});
|
||||
});
|
||||
|
||||
suite('assignee field', () => {
|
||||
const dummyAccount = {
|
||||
_account_id: 1,
|
||||
name: 'bojack',
|
||||
};
|
||||
const change = {
|
||||
actions: {
|
||||
assignee: {enabled: false},
|
||||
},
|
||||
assignee: dummyAccount,
|
||||
};
|
||||
let deleteStub;
|
||||
let setStub;
|
||||
|
||||
setup(() => {
|
||||
deleteStub = sandbox.stub(element.$.restAPI, 'deleteAssignee');
|
||||
setStub = sandbox.stub(element.$.restAPI, 'setAssignee');
|
||||
});
|
||||
|
||||
test('changing change recomputes _assignee', () => {
|
||||
assert.isFalse(!!element._assignee.length);
|
||||
const change = element.change;
|
||||
change.assignee = dummyAccount;
|
||||
element._changeChanged(change);
|
||||
assert.deepEqual(element._assignee[0], dummyAccount);
|
||||
});
|
||||
|
||||
test('modifying _assignee calls API', () => {
|
||||
assert.isFalse(!!element._assignee.length);
|
||||
element.set('_assignee', [dummyAccount]);
|
||||
assert.isTrue(setStub.calledOnce);
|
||||
assert.deepEqual(element.change.assignee, dummyAccount);
|
||||
element.set('_assignee', [dummyAccount]);
|
||||
assert.isTrue(setStub.calledOnce);
|
||||
element.set('_assignee', []);
|
||||
assert.isTrue(deleteStub.calledOnce);
|
||||
assert.equal(element.change.assignee, undefined);
|
||||
element.set('_assignee', []);
|
||||
assert.isTrue(deleteStub.calledOnce);
|
||||
});
|
||||
|
||||
test('_computeAssigneeReadOnly', () => {
|
||||
let mutable = false;
|
||||
assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
|
||||
mutable = true;
|
||||
assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
|
||||
change.actions.assignee.enabled = true;
|
||||
assert.isFalse(element._computeAssigneeReadOnly(mutable, change));
|
||||
mutable = false;
|
||||
assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('plugin endpoints', () => {
|
||||
@ -678,105 +610,5 @@ limitations under the License.
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('label colors', () => {
|
||||
test('valueless label rejected', () => {
|
||||
element.change = {
|
||||
labels: {
|
||||
'Do-Not-Submit': {
|
||||
rejected: {name: 'someone'},
|
||||
},
|
||||
},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
const labels = Polymer.dom(element.root).querySelectorAll('gr-label');
|
||||
assert.isTrue(labels[0].classList.contains('negative'));
|
||||
});
|
||||
|
||||
test('valueless label approved', () => {
|
||||
element.change = {
|
||||
labels: {
|
||||
'To-The-Infinity': {
|
||||
approved: {name: 'someone'},
|
||||
},
|
||||
},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
const labels = Polymer.dom(element.root).querySelectorAll('gr-label');
|
||||
assert.isTrue(labels[0].classList.contains('positive'));
|
||||
});
|
||||
|
||||
test('-2 to +2', () => {
|
||||
element.change = {
|
||||
labels: {
|
||||
'Code-Review': {
|
||||
all: [
|
||||
{value: 2, name: 'user 2'},
|
||||
{value: 1, name: 'user 1'},
|
||||
{value: -1, name: 'user 3'},
|
||||
{value: -2, name: 'user 4'},
|
||||
],
|
||||
values: {
|
||||
'-2': 'Awful',
|
||||
'-1': 'Don\'t submit as-is',
|
||||
' 0': 'No score',
|
||||
'+1': 'Looks good to me',
|
||||
'+2': 'Ready to submit',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
const labels = Polymer.dom(element.root).querySelectorAll('gr-label');
|
||||
assert.isTrue(labels[0].classList.contains('max'));
|
||||
assert.isTrue(labels[1].classList.contains('positive'));
|
||||
assert.isTrue(labels[2].classList.contains('negative'));
|
||||
assert.isTrue(labels[3].classList.contains('min'));
|
||||
});
|
||||
|
||||
test('-1 to +1', () => {
|
||||
element.change = {
|
||||
labels: {
|
||||
CI: {
|
||||
all: [
|
||||
{value: 1, name: 'user 1'},
|
||||
{value: -1, name: 'user 2'},
|
||||
],
|
||||
values: {
|
||||
'-1': 'Don\'t submit as-is',
|
||||
' 0': 'No score',
|
||||
'+1': 'Looks good to me',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
const labels = Polymer.dom(element.root).querySelectorAll('gr-label');
|
||||
assert.isTrue(labels[0].classList.contains('max'));
|
||||
assert.isTrue(labels[1].classList.contains('min'));
|
||||
});
|
||||
|
||||
test('0 to +2', () => {
|
||||
element.change = {
|
||||
labels: {
|
||||
CI: {
|
||||
all: [
|
||||
{value: 1, name: 'user 2'},
|
||||
{value: 2, name: 'user '},
|
||||
],
|
||||
values: {
|
||||
' 0': 'Don\'t submit as-is',
|
||||
'+1': 'No score',
|
||||
'+2': 'Looks good to me',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
const labels = Polymer.dom(element.root).querySelectorAll('gr-label');
|
||||
assert.isTrue(labels[0].classList.contains('positive'));
|
||||
assert.isTrue(labels[1].classList.contains('max'));
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -18,60 +18,156 @@ limitations under the License.
|
||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
|
||||
<link rel="import" href="../../shared/gr-label/gr-label.html">
|
||||
<link rel="import" href="../../shared/gr-label-info/gr-label-info.html">
|
||||
<link rel="import" href="../../shared/gr-limited-text/gr-limited-text.html">
|
||||
|
||||
<dom-module id="gr-change-requirements">
|
||||
<template strip-whitespace>
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
.status {
|
||||
color: #FFA62F;
|
||||
display: inline-block;
|
||||
font-weight: initial;
|
||||
font-family: var(--monospace-font-family);
|
||||
text-align: center;
|
||||
}
|
||||
.unsatisfied .icon {
|
||||
color: #FFA62F;
|
||||
.approved.status {
|
||||
color: var(--vote-text-color-recommended);
|
||||
}
|
||||
.satisfied .icon {
|
||||
color: #388E3C;
|
||||
.rejected.status {
|
||||
color: var(--vote-text-color-disliked);
|
||||
}
|
||||
.requirement {
|
||||
padding: .1em 0;
|
||||
iron-icon {
|
||||
color: inherit;
|
||||
}
|
||||
.requirementContainer:not(:first-of-type) {
|
||||
margin-top: .25em;
|
||||
.name {
|
||||
font-family: var(--font-family-bold);
|
||||
}
|
||||
.labelName, .changeIsWip {
|
||||
section {
|
||||
display: table-row;
|
||||
}
|
||||
.show-hide {
|
||||
float: right;
|
||||
}
|
||||
.title {
|
||||
font-weight: bold;
|
||||
min-width: 10em;
|
||||
padding-left: var(--requirements-horizontal-padding);
|
||||
padding-right: .5em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.value {
|
||||
padding-right: var(--requirements-horizontal-padding);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.title,
|
||||
.value {
|
||||
display: table-cell;
|
||||
padding-top: .6em;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.showHide {
|
||||
cursor: pointer;
|
||||
}
|
||||
.showHide .title {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-bottom: .5em;
|
||||
padding-top: .5em;
|
||||
}
|
||||
.showHide .value {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.showHide iron-icon {
|
||||
color: var(--deemphasized-text-color);
|
||||
float: right;
|
||||
}
|
||||
.spacer {
|
||||
height: .5em;
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[_showWip]]">
|
||||
<div class="requirement unsatisfied changeIsWip">
|
||||
<span class="status"><iron-icon class="icon" icon="gr-icons:hourglass"></iron-icon></span>
|
||||
Work in Progress
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showLabels]]">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[labels]]">
|
||||
<div class$="requirement [[item.style]]">
|
||||
<span class="status">
|
||||
<iron-icon class="icon" icon="[[item.icon]]"></iron-icon>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_requirements]]">
|
||||
<section>
|
||||
<div class="title requirement">
|
||||
<span class$="status [[item.style]]">
|
||||
<iron-icon class="icon" icon="[[_computeRequirementIcon(item.satisfied)]]"></iron-icon>
|
||||
</span>
|
||||
Label <span class="labelName">[[item.label]]</span>
|
||||
<gr-limited-text class="name" limit="40" text="[[item.fallback_text]]"></gr-limited-text>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[requirements]]">
|
||||
<div class$="requirement [[_computeRequirementClass(item.satisfied)]]">
|
||||
<span class="status">
|
||||
<iron-icon class="icon" icon="[[_computeRequirementIcon(item.satisfied)]]"></iron-icon>
|
||||
</span>
|
||||
[[item.fallback_text]]
|
||||
</div>
|
||||
items="[[_requiredLabels]]">
|
||||
<section>
|
||||
<div class="title">
|
||||
<span class$="status [[item.style]]">
|
||||
<iron-icon class="icon" icon="[[item.icon]]"></iron-icon>
|
||||
</span>
|
||||
<gr-limited-text class="name" limit="40" text="[[item.label]]"></gr-limited-text>
|
||||
</div>
|
||||
<div class="value">
|
||||
<gr-label-info
|
||||
change="{{change}}"
|
||||
account="[[account]]"
|
||||
mutable="[[mutable]]"
|
||||
label="[[item.label]]"
|
||||
label-info="[[item.labelInfo]]"></gr-label-info>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<section class="spacer"></section>
|
||||
<section class$="spacer [[_computeShowOptional(_optionalLabels.*)]]"></section>
|
||||
<section
|
||||
show-bottom-border$="[[_showOptionalLabels]]"
|
||||
on-tap="_handleShowHide"
|
||||
class$="showHide [[_computeShowOptional(_optionalLabels.*)]]">
|
||||
<div class="title">Other labels</div>
|
||||
<div class="value">
|
||||
<iron-icon
|
||||
id="showHide"
|
||||
icon="[[_computeShowHideIcon(_showOptionalLabels)]]">
|
||||
</iron-icon>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_optionalLabels]]">
|
||||
<section class$="optional [[_computeSectionClass(_showOptionalLabels)]]">
|
||||
<div class="title">
|
||||
<span class$="status [[item.style]]">
|
||||
<template is="dom-if" if="[[item.icon]]">
|
||||
<iron-icon class="icon" icon="[[item.icon]]"></iron-icon>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!item.icon]]">
|
||||
<span>[[_computeLabelValue(item.labelInfo.value)]]</span>
|
||||
</template>
|
||||
</span>
|
||||
<gr-limited-text class="name" limit="40" text="[[item.label]]"></gr-limited-text>
|
||||
</div>
|
||||
<div class="value">
|
||||
<gr-label-info
|
||||
change="{{change}}"
|
||||
account="[[account]]"
|
||||
mutable="[[mutable]]"
|
||||
label="[[item.label]]"
|
||||
label-info="[[item.labelInfo]]"></gr-label-info>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<section class$="spacer [[_computeShowOptional(_optionalLabels.*)]] [[_computeSectionClass(_showOptionalLabels)]]"></section>
|
||||
</template>
|
||||
<script src="gr-change-requirements.js"></script>
|
||||
</dom-module>
|
||||
|
@ -17,29 +17,33 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const VALID_SELECTOR_REGEX = /[^A-Za-z0-9\-]/g;
|
||||
|
||||
Polymer({
|
||||
is: 'gr-change-requirements',
|
||||
|
||||
properties: {
|
||||
/** @type {?} */
|
||||
change: Object,
|
||||
requirements: {
|
||||
account: Object,
|
||||
mutable: Boolean,
|
||||
_requirements: {
|
||||
type: Array,
|
||||
computed: '_computeRequirements(change)',
|
||||
},
|
||||
labels: {
|
||||
_requiredLabels: {
|
||||
type: Array,
|
||||
computed: '_computeLabels(change)',
|
||||
value: () => [],
|
||||
},
|
||||
_optionalLabels: {
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
_showWip: {
|
||||
type: Boolean,
|
||||
computed: '_computeShowWip(change)',
|
||||
},
|
||||
_showLabels: {
|
||||
_showOptionalLabels: {
|
||||
type: Boolean,
|
||||
computed: '_computeShowLabelStatus(change)',
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
|
||||
@ -47,9 +51,9 @@
|
||||
Gerrit.RESTClientBehavior,
|
||||
],
|
||||
|
||||
_computeShowLabelStatus(change) {
|
||||
return change.status === this.ChangeStatus.NEW;
|
||||
},
|
||||
observers: [
|
||||
'_computeLabels(change.labels.*)',
|
||||
],
|
||||
|
||||
_computeShowWip(change) {
|
||||
return change.work_in_progress;
|
||||
@ -61,48 +65,85 @@
|
||||
if (change.requirements) {
|
||||
for (const requirement of change.requirements) {
|
||||
requirement.satisfied = requirement.status === 'OK';
|
||||
requirement.style = this._computeRequirementClass(requirement);
|
||||
_requirements.push(requirement);
|
||||
}
|
||||
}
|
||||
if (change.work_in_progress) {
|
||||
_requirements.push({
|
||||
fallback_text: 'Work-in-progress',
|
||||
tooltip: 'Change must not be in \'Work in Progress\' state.',
|
||||
});
|
||||
}
|
||||
|
||||
return _requirements;
|
||||
},
|
||||
|
||||
_computeLabels(change) {
|
||||
const labels = change.labels;
|
||||
const _labels = [];
|
||||
|
||||
for (const label in labels) {
|
||||
if (!labels.hasOwnProperty(label)) { continue; }
|
||||
const obj = labels[label];
|
||||
if (obj.optional) { continue; }
|
||||
|
||||
const icon = this._computeRequirementIcon(obj.approved);
|
||||
const style = this._computeRequirementClass(obj.approved);
|
||||
_labels.push({label, icon, style});
|
||||
}
|
||||
|
||||
return _labels;
|
||||
},
|
||||
|
||||
_computeRequirementClass(requirementStatus) {
|
||||
if (requirementStatus) {
|
||||
return 'satisfied';
|
||||
} else {
|
||||
return 'unsatisfied';
|
||||
}
|
||||
return requirementStatus ? 'approved' : '';
|
||||
},
|
||||
|
||||
_computeRequirementIcon(requirementStatus) {
|
||||
if (requirementStatus) {
|
||||
return 'gr-icons:check';
|
||||
} else {
|
||||
return 'gr-icons:hourglass';
|
||||
return requirementStatus ? 'gr-icons:check' : 'gr-icons:hourglass';
|
||||
},
|
||||
|
||||
_computeLabels(labelsRecord) {
|
||||
const labels = labelsRecord.base;
|
||||
this._optionalLabels = [];
|
||||
this._requiredLabels = [];
|
||||
|
||||
for (const label in labels) {
|
||||
if (!labels.hasOwnProperty(label)) { continue; }
|
||||
|
||||
const labelInfo = labels[label];
|
||||
const icon = this._computeLabelIcon(labelInfo);
|
||||
const style = this._computeLabelClass(labelInfo);
|
||||
const path = labelInfo.optional ? '_optionalLabels' : '_requiredLabels';
|
||||
|
||||
this.push(path, {label, icon, style, labelInfo});
|
||||
}
|
||||
},
|
||||
|
||||
_removeInvalidChars(text) {
|
||||
return text.replace(VALID_SELECTOR_REGEX, '');
|
||||
/**
|
||||
* @param {Object} labelInfo
|
||||
* @return {string} The icon name, or undefined if no icon should
|
||||
* be used.
|
||||
*/
|
||||
_computeLabelIcon(labelInfo) {
|
||||
if (labelInfo.approved) { return 'gr-icons:check'; }
|
||||
if (labelInfo.rejected) { return 'gr-icons:close'; }
|
||||
return 'gr-icons:hourglass';
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} labelInfo
|
||||
*/
|
||||
_computeLabelClass(labelInfo) {
|
||||
if (labelInfo.approved) { return 'approved'; }
|
||||
if (labelInfo.rejected) { return 'rejected'; }
|
||||
return '';
|
||||
},
|
||||
|
||||
_computeShowOptional(optionalFieldsRecord) {
|
||||
return optionalFieldsRecord.base.length ? '' : 'hidden';
|
||||
},
|
||||
|
||||
_computeLabelValue(value) {
|
||||
return (value > 0 ? '+' : '') + value;
|
||||
},
|
||||
|
||||
_computeShowHideIcon(showOptionalLabels) {
|
||||
return showOptionalLabels ?
|
||||
'gr-icons:expand-less' :
|
||||
'gr-icons:expand-more';
|
||||
},
|
||||
|
||||
_computeSectionClass(show) {
|
||||
return show ? '' : 'hidden';
|
||||
},
|
||||
|
||||
_handleShowHide(e) {
|
||||
this._showOptionalLabels = !this._showOptionalLabels;
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
@ -40,19 +40,69 @@ limitations under the License.
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('computed fields', () => {
|
||||
assert.isTrue(element._computeShowLabelStatus({status: 'NEW'}));
|
||||
assert.isFalse(element._computeShowLabelStatus({status: 'MERGED'}));
|
||||
assert.isFalse(element._computeShowLabelStatus({status: 'ABANDONED'}));
|
||||
|
||||
test('requirements computed fields', () => {
|
||||
assert.isTrue(element._computeShowWip({work_in_progress: true}));
|
||||
assert.isFalse(element._computeShowWip({work_in_progress: false}));
|
||||
|
||||
assert.equal(element._computeRequirementClass(true), 'satisfied');
|
||||
assert.equal(element._computeRequirementClass(false), 'unsatisfied');
|
||||
assert.equal(element._computeRequirementClass(true), 'approved');
|
||||
assert.equal(element._computeRequirementClass(false), '');
|
||||
|
||||
assert.equal(element._computeRequirementIcon(true), 'gr-icons:check');
|
||||
assert.equal(element._computeRequirementIcon(false), 'gr-icons:hourglass');
|
||||
assert.equal(element._computeRequirementIcon(false),
|
||||
'gr-icons:hourglass');
|
||||
});
|
||||
|
||||
test('label computed fields', () => {
|
||||
assert.equal(element._computeLabelIcon({approved: []}), 'gr-icons:check');
|
||||
assert.equal(element._computeLabelIcon({rejected: []}), 'gr-icons:close');
|
||||
assert.equal(element._computeLabelIcon({}), 'gr-icons:hourglass');
|
||||
|
||||
assert.equal(element._computeLabelClass({approved: []}), 'approved');
|
||||
assert.equal(element._computeLabelClass({rejected: []}), 'rejected');
|
||||
assert.equal(element._computeLabelClass({}), '');
|
||||
assert.equal(element._computeLabelClass({value: 0}), '');
|
||||
|
||||
assert.equal(element._computeLabelValue(1), '+1');
|
||||
assert.equal(element._computeLabelValue(-1), '-1');
|
||||
assert.equal(element._computeLabelValue(0), '0');
|
||||
});
|
||||
|
||||
test('_computeLabels', () => {
|
||||
assert.equal(element._optionalLabels.length, 0);
|
||||
assert.equal(element._requiredLabels.length, 0);
|
||||
element._computeLabels({base: {
|
||||
test: {
|
||||
all: [{_account_id: 1, name: 'bojack', value: 1}],
|
||||
default_value: 0,
|
||||
values: [],
|
||||
value: 1,
|
||||
},
|
||||
opt_test: {
|
||||
all: [{_account_id: 1, name: 'bojack', value: 1}],
|
||||
default_value: 0,
|
||||
values: [],
|
||||
optional: true,
|
||||
},
|
||||
}});
|
||||
assert.equal(element._optionalLabels.length, 1);
|
||||
assert.equal(element._requiredLabels.length, 1);
|
||||
|
||||
assert.equal(element._optionalLabels[0].label, 'opt_test');
|
||||
assert.equal(element._optionalLabels[0].icon, 'gr-icons:hourglass');
|
||||
assert.equal(element._optionalLabels[0].style, '');
|
||||
assert.ok(element._optionalLabels[0].labelInfo);
|
||||
});
|
||||
|
||||
test('optional show/hide', () => {
|
||||
element._optionalLabels = [{label: 'test'}];
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.ok(element.$$('section.optional'));
|
||||
MockInteractions.tap(element.$$('.showHide'));
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isFalse(element._showOptionalLabels);
|
||||
assert.isTrue(isHidden(element.$$('section.optional')));
|
||||
});
|
||||
|
||||
test('properly converts satisfied labels', () => {
|
||||
@ -60,17 +110,16 @@ limitations under the License.
|
||||
status: 'NEW',
|
||||
labels: {
|
||||
Verified: {
|
||||
approved: true,
|
||||
approved: [],
|
||||
},
|
||||
},
|
||||
requirements: [],
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const labelName = element.$$('.satisfied .labelName');
|
||||
assert.ok(labelName);
|
||||
assert.isFalse(labelName.hasAttribute('hidden'));
|
||||
assert.equal(labelName.innerHTML, 'Verified');
|
||||
assert.ok(element.$$('.approved'));
|
||||
assert.ok(element.$$('.name'));
|
||||
assert.equal(element.$$('.name').text, 'Verified');
|
||||
});
|
||||
|
||||
test('properly converts unsatisfied labels', () => {
|
||||
@ -84,10 +133,10 @@ limitations under the License.
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const labelName = element.$$('.unsatisfied .labelName');
|
||||
assert.ok(labelName);
|
||||
assert.isFalse(labelName.hasAttribute('hidden'));
|
||||
assert.equal(labelName.innerHTML, 'Verified');
|
||||
const name = element.$$('.name');
|
||||
assert.ok(name);
|
||||
assert.isFalse(name.hasAttribute('hidden'));
|
||||
assert.equal(name.text, 'Verified');
|
||||
});
|
||||
|
||||
test('properly displays Work In Progress', () => {
|
||||
@ -99,13 +148,10 @@ limitations under the License.
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const changeIsWip = element.$$('.changeIsWip.unsatisfied');
|
||||
const changeIsWip = element.$$('.title');
|
||||
assert.ok(changeIsWip);
|
||||
assert.isFalse(changeIsWip.hasAttribute('hidden'));
|
||||
assert.notEqual(changeIsWip.innerHTML.indexOf('Work in Progress'), -1);
|
||||
});
|
||||
|
||||
|
||||
test('properly displays a satisfied requirement', () => {
|
||||
element.change = {
|
||||
status: 'NEW',
|
||||
@ -117,13 +163,12 @@ limitations under the License.
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const satisfiedRequirement = element.$$('.satisfied');
|
||||
assert.ok(satisfiedRequirement);
|
||||
assert.isFalse(satisfiedRequirement.hasAttribute('hidden'));
|
||||
|
||||
// Extract the content of the text node (second element, after the span)
|
||||
const textNode = satisfiedRequirement.childNodes[1].nodeValue.trim();
|
||||
assert.equal(textNode, 'Resolve all comments');
|
||||
const requirement = element.$$('.requirement');
|
||||
assert.ok(requirement);
|
||||
assert.isFalse(requirement.hasAttribute('hidden'));
|
||||
assert.ok(requirement.querySelector('.approved'));
|
||||
assert.equal(requirement.querySelector('.name').text,
|
||||
'Resolve all comments');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -109,19 +109,14 @@ limitations under the License.
|
||||
https://github.com/Polymer/polymer/issues/2531 */
|
||||
.container section.changeInfo {
|
||||
display: flex;
|
||||
padding: 0 var(--default-horizontal-margin);
|
||||
}
|
||||
.changeId {
|
||||
color: var(--deemphasized-text-color);
|
||||
font-family: var(--font-family);
|
||||
margin-top: 1em;
|
||||
}
|
||||
.changeInfo-column {
|
||||
padding: 0 1em;
|
||||
}
|
||||
.changeMetadata {
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding: 1em 0;
|
||||
}
|
||||
/* Prevent plugin text from overflowing. */
|
||||
#change_plugins {
|
||||
@ -200,6 +195,7 @@ limitations under the License.
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
margin: 1em 0;
|
||||
padding: 0 1em;
|
||||
}
|
||||
.collapseToggleContainer {
|
||||
display: flex;
|
||||
@ -253,7 +249,8 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
#metadata {
|
||||
padding-right: 1em;
|
||||
--metadata-horizontal-padding: 1em;
|
||||
padding-top: 1em;
|
||||
width: 100%;
|
||||
}
|
||||
/* NOTE: If you update this breakpoint, also update the
|
||||
@ -318,19 +315,18 @@ limitations under the License.
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.commitContainer {
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin: 0;
|
||||
padding: 1em 0;
|
||||
padding: 1em;
|
||||
}
|
||||
.relatedChanges,
|
||||
.changeMetadata {
|
||||
font-size: var(--font-size-normal);
|
||||
}
|
||||
.changeMetadata {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-right: none;
|
||||
margin-top: .25em;
|
||||
max-width: none;
|
||||
padding: 1em 0;
|
||||
}
|
||||
#metadata,
|
||||
.mainChangeInfo {
|
||||
@ -419,10 +415,10 @@ limitations under the License.
|
||||
<gr-change-metadata
|
||||
id="metadata"
|
||||
change="{{_change}}"
|
||||
account="[[_account]]"
|
||||
revision="[[_selectedRevision]]"
|
||||
commit-info="[[_commitInfo]]"
|
||||
server-config="[[_serverConfig]]"
|
||||
mutable="[[_loggedIn]]"
|
||||
parent-is-current="[[_parentIsCurrent]]"
|
||||
on-show-reply-dialog="_handleShowReplyDialog">
|
||||
</gr-change-metadata>
|
||||
|
@ -150,10 +150,10 @@ limitations under the License.
|
||||
min-width: 3.5em;
|
||||
}
|
||||
.added {
|
||||
color: #388E3C;
|
||||
color: var(--vote-text-color-recommended);
|
||||
}
|
||||
.removed {
|
||||
color: #D32F2F;
|
||||
color: var(--vote-text-color-disliked);
|
||||
text-align: left;
|
||||
}
|
||||
.drafts {
|
||||
|
@ -83,7 +83,7 @@ limitations under the License.
|
||||
color: #1b5e20;
|
||||
}
|
||||
.submittableCheck {
|
||||
color: #388E3C;
|
||||
color: var(--vote-text-color-recommended);
|
||||
display: none;
|
||||
}
|
||||
.submittableCheck.submittable {
|
||||
|
@ -70,6 +70,7 @@ limitations under the License.
|
||||
.transparentBackground,
|
||||
gr-button.transparentBackground {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
:host([disabled]) {
|
||||
opacity: .6;
|
||||
|
@ -64,7 +64,7 @@ limitations under the License.
|
||||
[[_computeEmailStr(account)]]
|
||||
</span>
|
||||
<template is="dom-if" if="[[account.status]]">
|
||||
(<gr-limited-text limit="20" text="[[account.status]]"></gr-limited-text>)
|
||||
(<gr-limited-text limit="10" text="[[account.status]]"></gr-limited-text>)
|
||||
</template>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -30,12 +30,13 @@ limitations under the License.
|
||||
<style include="gr-voting-styles"></style>
|
||||
<style include="shared-styles">
|
||||
.title {
|
||||
font-size: var(--font-size-large);
|
||||
font-weight: bold;
|
||||
max-width: 20em;
|
||||
padding-right: .5em;
|
||||
word-break: break-word;
|
||||
}
|
||||
.placeholder {
|
||||
color: var(--deemphasized-text-color);
|
||||
padding-top: .5em;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
@ -44,7 +45,7 @@ limitations under the License.
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-right: .3em;
|
||||
padding: .2em .85em;
|
||||
padding: .05em .85em;
|
||||
@apply --vote-chip-styles;
|
||||
}
|
||||
.max {
|
||||
@ -68,16 +69,6 @@ limitations under the License.
|
||||
tr {
|
||||
min-height: 2.25em;
|
||||
}
|
||||
tr td {
|
||||
padding-top: .35em;
|
||||
}
|
||||
tr.currentUser td {
|
||||
padding-bottom: .5em;
|
||||
}
|
||||
tr.currentUser + tr td {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: .5em;
|
||||
}
|
||||
gr-button {
|
||||
--gr-button: {
|
||||
height: 2em;
|
||||
@ -86,24 +77,25 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
gr-account-chip {
|
||||
margin-right: 1.5em;
|
||||
margin-right: .25em;
|
||||
}
|
||||
iron-icon {
|
||||
height: 1.2em;
|
||||
width: 1.2em;
|
||||
}
|
||||
.labelValueContainer:not(:first-of-type) td {
|
||||
padding-top: .3em;
|
||||
}
|
||||
</style>
|
||||
<p class="title">[[label]]</p>
|
||||
<p class$="placeholder [[_computeShowPlaceholder(labelInfo, change.labels.*)]]">
|
||||
No votes for this label.
|
||||
</p>
|
||||
<table>
|
||||
<p class$="placeholder [[_computeShowPlaceholder(labelInfo, change.labels.*)]]">
|
||||
No votes.
|
||||
</p>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_mapLabelInfo(labelInfo, account, change.labels.*)]]"
|
||||
as="mappedLabel">
|
||||
<tr class$="labelValueContainer [[_computeLabelContainerClass(mappedLabel)]]">
|
||||
<td>
|
||||
<gr-account-chip
|
||||
account="[[mappedLabel.account]]"
|
||||
transparent-background></gr-account-chip>
|
||||
</td>
|
||||
<tr class="labelValueContainer">
|
||||
<td>
|
||||
<gr-label
|
||||
has-tooltip
|
||||
@ -112,6 +104,11 @@ limitations under the License.
|
||||
[[mappedLabel.value]]
|
||||
</gr-label>
|
||||
</td>
|
||||
<td>
|
||||
<gr-account-chip
|
||||
account="[[mappedLabel.account]]"
|
||||
transparent-background></gr-account-chip>
|
||||
</td>
|
||||
<td>
|
||||
<gr-button
|
||||
link
|
||||
|
@ -71,20 +71,16 @@
|
||||
labelClassName = 'negative';
|
||||
}
|
||||
}
|
||||
const formattedLabel = {
|
||||
value: labelValPrefix + label.value,
|
||||
className: labelClassName,
|
||||
account: label,
|
||||
};
|
||||
if (label._account_id === account._account_id) {
|
||||
// Put self-votes at the top, and add a flag.
|
||||
result.unshift({
|
||||
value: labelValPrefix + label.value,
|
||||
className: labelClassName,
|
||||
account: label,
|
||||
isCurrentUser: true,
|
||||
});
|
||||
// Put self-votes at the top.
|
||||
result.unshift(formattedLabel);
|
||||
} else {
|
||||
result.push({
|
||||
value: labelValPrefix + label.value,
|
||||
className: labelClassName,
|
||||
account: label,
|
||||
});
|
||||
result.push(formattedLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,10 +161,6 @@
|
||||
return labelInfo.values[score];
|
||||
},
|
||||
|
||||
_computeLabelContainerClass(label) {
|
||||
return label.isCurrentUser ? 'currentUser' : '';
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {!Object} labelInfo
|
||||
* @param {Object} changeLabelsRecord not used, but added as a parameter in
|
||||
@ -177,7 +169,9 @@
|
||||
_computeShowPlaceholder(labelInfo, changeLabelsRecord) {
|
||||
if (labelInfo.all) {
|
||||
for (const label of labelInfo.all) {
|
||||
if (label.value) { return 'hidden'; }
|
||||
if (label.value && label.value != labelInfo.default_value) {
|
||||
return 'hidden';
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
|
@ -21,9 +21,9 @@ limitations under the License.
|
||||
:host {
|
||||
--vote-chip-styles: {
|
||||
border: 1px solid rgba(0,0,0,.12);
|
||||
border-radius: 12px;
|
||||
border-radius: 1em;
|
||||
box-shadow: none;
|
||||
min-width: 40px;
|
||||
min-width: 3em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -76,6 +76,9 @@ limitations under the License.
|
||||
--vote-color-disliked: #f7c4cb;
|
||||
--vote-color-neutral: #ebf5fb;
|
||||
|
||||
--vote-text-color-recommended: #388E3C;
|
||||
--vote-text-color-disliked: #D32F2F;
|
||||
|
||||
/* Diff colors */
|
||||
--diff-selection-background-color: #c7dbf9;
|
||||
--light-remove-highlight-color: #FFEBEE;
|
||||
|
@ -63,6 +63,7 @@ limitations under the License.
|
||||
'change/gr-change-actions/gr-change-actions_test.html',
|
||||
'change/gr-change-metadata/gr-change-metadata-it_test.html',
|
||||
'change/gr-change-metadata/gr-change-metadata_test.html',
|
||||
'change/gr-change-requirements/gr-change-requirements_test.html',
|
||||
'change/gr-change-view/gr-change-view_test.html',
|
||||
'change/gr-comment-list/gr-comment-list_test.html',
|
||||
'change/gr-commit-info/gr-commit-info_test.html',
|
||||
|
Loading…
Reference in New Issue
Block a user