Merge "Fix and update toast colors"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.ts
index a9b4ece..a0aa962 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.ts
@@ -224,7 +224,7 @@
hidden$="[[_computeIsColumnHidden('Repo', visibleChangeTableColumns)]]"
>
<a class="fullRepo" href$="[[_computeRepoUrl(change)]]">
- <div class="content">[[_computeRepoDisplay(change)]]</div>
+ [[_computeRepoDisplay(change)]]
</a>
<a
class="truncatedRepo"
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
index 96a19e0..aa76347 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.js
@@ -128,7 +128,7 @@
// Open confirmation dialog and tap confirm button.
await element.$.confirmDeleteOverlay.open();
- MockInteractions.tap(element.$.confirmDeleteDialog.$.confirm);
+ MockInteractions.tap(element.$.confirmDeleteDialog.confirmButton);
flush();
assert.isTrue(deleteStub.calledWithExactly('-is:open'));
assert.isTrue(element.$.confirmDeleteDialog.disabled);
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
index 1256cc1..3df997f8 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.js
@@ -123,10 +123,11 @@
test('cherry pick topic submit', async () => {
element.branch = 'master';
+ await flush();
const executeChangeActionStub = stubRestApi(
'executeChangeAction').returns(Promise.resolve([]));
MockInteractions.tap(element.shadowRoot.
- querySelector('gr-dialog').$.confirm);
+ querySelector('gr-dialog').confirmButton);
await flush();
const args = executeChangeActionStub.args[0];
assert.equal(args[0], 1);
@@ -137,26 +138,29 @@
assert.isTrue(args[4].allow_empty);
});
- test('deselecting a change removes it from being cherry picked', () => {
- const duplicateChangesStub = sinon.stub(element,
- 'containsDuplicateProject');
- element.branch = 'master';
- const executeChangeActionStub = stubRestApi(
- 'executeChangeAction').returns(Promise.resolve([]));
- const checkboxes = element.shadowRoot.querySelectorAll(
- 'input[type="checkbox"]');
- assert.equal(checkboxes.length, 2);
- assert.isTrue(checkboxes[0].checked);
- MockInteractions.tap(checkboxes[0]);
- MockInteractions.tap(element.shadowRoot.
- querySelector('gr-dialog').$.confirm);
- flush();
- assert.equal(executeChangeActionStub.callCount, 1);
- assert.isTrue(duplicateChangesStub.called);
- });
+ test('deselecting a change removes it from being cherry picked',
+ async () => {
+ const duplicateChangesStub = sinon.stub(element,
+ 'containsDuplicateProject');
+ element.branch = 'master';
+ await flush();
+ const executeChangeActionStub = stubRestApi(
+ 'executeChangeAction').returns(Promise.resolve([]));
+ const checkboxes = element.shadowRoot.querySelectorAll(
+ 'input[type="checkbox"]');
+ assert.equal(checkboxes.length, 2);
+ assert.isTrue(checkboxes[0].checked);
+ MockInteractions.tap(checkboxes[0]);
+ MockInteractions.tap(element.shadowRoot.
+ querySelector('gr-dialog').confirmButton);
+ await flush();
+ assert.equal(executeChangeActionStub.callCount, 1);
+ assert.isTrue(duplicateChangesStub.called);
+ });
- test('deselecting all change shows error message', () => {
+ test('deselecting all change shows error message', async () => {
element.branch = 'master';
+ await flush();
const executeChangeActionStub = stubRestApi(
'executeChangeAction').returns(Promise.resolve([]));
const checkboxes = element.shadowRoot.querySelectorAll(
@@ -165,8 +169,8 @@
MockInteractions.tap(checkboxes[0]);
MockInteractions.tap(checkboxes[1]);
MockInteractions.tap(element.shadowRoot.
- querySelector('gr-dialog').$.confirm);
- flush();
+ querySelector('gr-dialog').confirmButton);
+ await flush();
assert.equal(executeChangeActionStub.callCount, 0);
assert.equal(element.shadowRoot.querySelector('.error-message').innerText
, 'No change selected');
@@ -180,8 +184,8 @@
});
test('submit button is blocked while cherry picks is running', async () => {
- const confirmButton = element.shadowRoot.querySelector('gr-dialog').$
- .confirm;
+ const confirmButton = element.shadowRoot.querySelector('gr-dialog')
+ .confirmButton;
assert.isTrue(confirmButton.hasAttribute('disabled'));
element.branch = 'b';
await flush();
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
index d115899..dec65e2 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
@@ -58,6 +58,7 @@
gr-vote-chip {
--gr-vote-chip-width: 14px;
--gr-vote-chip-height: 14px;
+ margin-right: var(--spacing-s);
}
</style>
<div class="container">
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 19bc388..21e8093 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -21,6 +21,7 @@
import {
AccountInfo,
isDetailedLabelInfo,
+ LabelInfo,
LabelNameToInfoMap,
SubmitRequirementResultInfo,
SubmitRequirementStatus,
@@ -28,6 +29,7 @@
import {unique} from '../../../utils/common-util';
import {
extractAssociatedLabels,
+ getAllUniqueApprovals,
hasVotes,
iconForStatus,
} from '../../../utils/label-util';
@@ -92,9 +94,19 @@
visibility: visible;
}
.requirements,
- section.votes {
+ section.trigger-votes {
margin-left: var(--spacing-l);
}
+ .trigger-votes {
+ padding-top: var(--spacing-s);
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-s);
+ /* Setting max-width as defined in Submit Requirements design,
+ * to wrap overflowed items to next row.
+ */
+ max-width: 390px;
+ }
gr-limited-text.name {
font-weight: var(--font-weight-bold);
}
@@ -115,6 +127,9 @@
color: var(--error-foreground);
vertical-align: top;
}
+ gr-vote-chip {
+ margin-right: var(--spacing-s);
+ }
`,
];
}
@@ -211,12 +226,7 @@
renderLabelVote(label: string, labels: LabelNameToInfoMap) {
const labelInfo = labels[label];
if (!isDetailedLabelInfo(labelInfo)) return;
- const uniqueApprovals = (labelInfo.all ?? [])
- .filter(
- (approvalInfo, index, array) =>
- index === array.findIndex(other => other.value === approvalInfo.value)
- )
- .sort((a, b) => -(a.value ?? 0) + (b.value ?? 0));
+ const uniqueApprovals = getAllUniqueApprovals(labelInfo);
return uniqueApprovals.map(
approvalInfo =>
html`<gr-vote-chip
@@ -262,16 +272,13 @@
.filter(label => hasVotes(labels[label]));
if (!triggerVotes.length) return;
return html`<h3 class="metadata-title heading-3">Trigger Votes</h3>
- <section class="votes">
+ <section class="trigger-votes">
${triggerVotes.map(
- label => html`${label}:
- <gr-label-info
- .change="${this.change}"
- .account="${this.account}"
- .mutable="${this.mutable ?? false}"
- label="${label}"
+ label =>
+ html`<gr-trigger-vote
+ .label="${label}"
.labelInfo="${labels[label]}"
- ></gr-label-info>`
+ ></gr-trigger-vote>`
)}
</section>`;
}
@@ -320,8 +327,61 @@
}
}
+@customElement('gr-trigger-vote')
+export class GrTriggerVote extends LitElement {
+ @property()
+ label?: string;
+
+ @property({type: Object})
+ labelInfo?: LabelInfo;
+
+ static override get styles() {
+ return css`
+ :host {
+ display: block;
+ }
+ .container {
+ box-sizing: border-box;
+ border: 1px solid var(--border-color);
+ border-radius: calc(var(--border-radius) + 2px);
+ background-color: var(--background-color-primary);
+ display: flex;
+ padding: 0;
+ padding-left: var(--spacing-s);
+ padding-right: var(--spacing-xxs);
+ align-items: center;
+ }
+ .label {
+ padding-right: var(--spacing-s);
+ font-weight: var(--font-weight-bold);
+ }
+ gr-vote-chip {
+ --gr-vote-chip-width: 14px;
+ --gr-vote-chip-height: 14px;
+ margin-right: 0px;
+ }
+ `;
+ }
+
+ override render() {
+ const uniqueApprovals = getAllUniqueApprovals(this.labelInfo);
+ return html`
+ <div class="container">
+ <span class="label">${this.label}</span>
+ ${uniqueApprovals.map(
+ approvalInfo => html`<gr-vote-chip
+ .vote="${approvalInfo}"
+ .label="${this.labelInfo}"
+ ></gr-vote-chip>`
+ )}
+ </div>
+ `;
+ }
+}
+
declare global {
interface HTMLElementTagNameMap {
'gr-submit-requirements': GrSubmitRequirements;
+ 'gr-trigger-vote': GrTriggerVote;
}
}
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index e1d8932..a643c18 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -469,6 +469,11 @@
.testing:hover * {
visibility: visible;
}
+ .zero {
+ padding: var(--spacing-m) 0;
+ color: var(--primary-text-color);
+ margin-top: var(--spacing-m);
+ }
.login,
.error {
padding: var(--spacing-m);
@@ -528,7 +533,7 @@
<div class="flex-space"></div>
${this.renderTitleButtons()} ${this.renderCollapseButton()}
</h2>
- ${this.renderErrors()} ${this.renderSignIn()}
+ ${this.renderErrors()} ${this.renderSignIn()} ${this.renderZeroState()}
<input
id="filterInput"
type="text"
@@ -542,6 +547,11 @@
`;
}
+ private renderZeroState() {
+ if (this.runs.length > 0) return;
+ return html`<div class="zero">No Check Run to show</div>`;
+ }
+
private renderErrors() {
return Object.entries(this.errorMessages).map(
([plugin, message]) =>
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.ts b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.ts
index 2dec8d2..c51988e 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.ts
@@ -26,15 +26,16 @@
suite('gr-error-dialog tests', () => {
let element: GrErrorDialog;
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await flush();
});
test('dismiss tap fires event', async () => {
const dismissCalled = mockPromise();
element.addEventListener('dismiss', () => dismissCalled.resolve());
MockInteractions.tap(
- (queryAndAssert(element, '#dialog') as GrDialog).$.confirm
+ (queryAndAssert(element, '#dialog') as GrDialog).confirmButton!
);
await dismissCalled;
});
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
index 2e1fc21..0ba68e2 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
@@ -76,33 +76,33 @@
assert.isTrue(element._isValidPath('test.js'));
});
- test('open', () => {
+ test('open', async () => {
assert.isFalse(hideDialogStub.called);
MockInteractions.tap(queryAndAssert(element, '#open'));
element.patchNum = 1 as PatchSetNum;
- return showDialogSpy.lastCall.returnValue.then(() => {
- assert.isTrue(hideDialogStub.called);
- assert.isTrue(element.$.openDialog.disabled);
- assert.isFalse(queryStub.called);
- // Setup _focused manually - in headless mode Chrome sometimes don't
- // setup focus. flush and/or flushAsynchronousOperations don't help
- openAutoComplete._focused = true;
- openAutoComplete.noDebounce = true;
- openAutoComplete.text = 'src/test.cpp';
- assert.isTrue(queryStub.called);
- assert.isFalse(element.$.openDialog.disabled);
- MockInteractions.tap(
- queryAndAssert(element.$.openDialog, 'gr-button[primary]')
- );
- assert.isTrue(editDiffStub.called);
- assert.isTrue(navStub.called);
- assert.deepEqual(editDiffStub.lastCall.args, [
- element.change,
- 'src/test.cpp',
- element.patchNum,
- ]);
- assert.isTrue(closeDialogSpy.called);
- });
+ await showDialogSpy.lastCall.returnValue;
+ assert.isTrue(hideDialogStub.called);
+ assert.isTrue(element.$.openDialog.disabled);
+ assert.isFalse(queryStub.called);
+ // Setup _focused manually - in headless mode Chrome sometimes don't
+ // setup focus. flush and/or flushAsynchronousOperations don't help
+ openAutoComplete._focused = true;
+ openAutoComplete.noDebounce = true;
+ openAutoComplete.text = 'src/test.cpp';
+ await flush();
+ assert.isTrue(queryStub.called);
+ assert.isFalse(element.$.openDialog.disabled);
+ MockInteractions.tap(
+ queryAndAssert(element.$.openDialog, 'gr-button[primary]')
+ );
+ assert.isTrue(editDiffStub.called);
+ assert.isTrue(navStub.called);
+ assert.deepEqual(editDiffStub.lastCall.args, [
+ element.change,
+ 'src/test.cpp',
+ element.patchNum,
+ ]);
+ assert.isTrue(closeDialogSpy.called);
});
test('cancel', () => {
@@ -133,59 +133,56 @@
element.$.deleteDialog!.querySelector('gr-autocomplete')!;
});
- test('delete', () => {
+ test('delete', async () => {
deleteStub.returns(Promise.resolve({ok: true}));
MockInteractions.tap(queryAndAssert(element, '#delete'));
- return showDialogSpy.lastCall.returnValue.then(() => {
- assert.isTrue(element.$.deleteDialog.disabled);
- assert.isFalse(queryStub.called);
- // Setup _focused manually - in headless mode Chrome sometimes don't
- // setup focus. flush and/or flushAsynchronousOperations don't help
- deleteAutocomplete._focused = true;
- deleteAutocomplete.noDebounce = true;
- deleteAutocomplete.text = 'src/test.cpp';
- assert.isTrue(queryStub.called);
- assert.isFalse(element.$.deleteDialog.disabled);
- MockInteractions.tap(
- queryAndAssert(element.$.deleteDialog, 'gr-button[primary]')
- );
- flush();
+ await showDialogSpy.lastCall.returnValue;
+ assert.isTrue(element.$.deleteDialog.disabled);
+ assert.isFalse(queryStub.called);
+ // Setup _focused manually - in headless mode Chrome sometimes don't
+ // setup focus. flush and/or flushAsynchronousOperations don't help
+ deleteAutocomplete._focused = true;
+ deleteAutocomplete.noDebounce = true;
+ deleteAutocomplete.text = 'src/test.cpp';
+ await flush();
+ assert.isTrue(queryStub.called);
+ assert.isFalse(element.$.deleteDialog.disabled);
+ MockInteractions.tap(
+ queryAndAssert(element.$.deleteDialog, 'gr-button[primary]')
+ );
+ await flush();
- assert.isTrue(deleteStub.called);
-
- return deleteStub.lastCall.returnValue.then(() => {
- assert.equal(element._path, '');
- assert.isTrue(navStub.called);
- assert.isTrue(closeDialogSpy.called);
- });
- });
+ assert.isTrue(deleteStub.called);
+ await deleteStub.lastCall.returnValue;
+ assert.equal(element._path, '');
+ assert.isTrue(navStub.called);
+ assert.isTrue(closeDialogSpy.called);
});
- test('delete fails', () => {
+ test('delete fails', async () => {
deleteStub.returns(Promise.resolve({ok: false}));
MockInteractions.tap(queryAndAssert(element, '#delete'));
- return showDialogSpy.lastCall.returnValue.then(() => {
- assert.isTrue(element.$.deleteDialog.disabled);
- assert.isFalse(queryStub.called);
- // Setup _focused manually - in headless mode Chrome sometimes don't
- // setup focus. flush and/or flushAsynchronousOperations don't help
- deleteAutocomplete._focused = true;
- deleteAutocomplete.noDebounce = true;
- deleteAutocomplete.text = 'src/test.cpp';
- assert.isTrue(queryStub.called);
- assert.isFalse(element.$.deleteDialog.disabled);
- MockInteractions.tap(
- queryAndAssert(element.$.deleteDialog, 'gr-button[primary]')
- );
- flush();
+ await showDialogSpy.lastCall.returnValue;
+ assert.isTrue(element.$.deleteDialog.disabled);
+ assert.isFalse(queryStub.called);
+ // Setup _focused manually - in headless mode Chrome sometimes don't
+ // setup focus. flush and/or flushAsynchronousOperations don't help
+ deleteAutocomplete._focused = true;
+ deleteAutocomplete.noDebounce = true;
+ deleteAutocomplete.text = 'src/test.cpp';
+ await flush();
+ assert.isTrue(queryStub.called);
+ assert.isFalse(element.$.deleteDialog.disabled);
+ MockInteractions.tap(
+ queryAndAssert(element.$.deleteDialog, 'gr-button[primary]')
+ );
+ await flush();
- assert.isTrue(deleteStub.called);
+ assert.isTrue(deleteStub.called);
- return deleteStub.lastCall.returnValue.then(() => {
- assert.isFalse(navStub.called);
- assert.isFalse(closeDialogSpy.called);
- });
- });
+ await deleteStub.lastCall.returnValue;
+ assert.isFalse(navStub.called);
+ assert.isFalse(closeDialogSpy.called);
});
test('cancel', () => {
@@ -217,67 +214,66 @@
element.$.renameDialog!.querySelector('gr-autocomplete')!;
});
- test('rename', () => {
+ test('rename', async () => {
renameStub.returns(Promise.resolve({ok: true}));
MockInteractions.tap(queryAndAssert(element, '#rename'));
- return showDialogSpy.lastCall.returnValue.then(() => {
- assert.isTrue(element.$.renameDialog.disabled);
- assert.isFalse(queryStub.called);
- // Setup _focused manually - in headless mode Chrome sometimes don't
- // setup focus. flush and/or flushAsynchronousOperations don't help
- renameAutocomplete._focused = true;
- renameAutocomplete.noDebounce = true;
- renameAutocomplete.text = 'src/test.cpp';
- assert.isTrue(queryStub.called);
- assert.isTrue(element.$.renameDialog.disabled);
+ await showDialogSpy.lastCall.returnValue;
+ assert.isTrue(element.$.renameDialog.disabled);
+ assert.isFalse(queryStub.called);
+ // Setup _focused manually - in headless mode Chrome sometimes don't
+ // setup focus. flush and/or flushAsynchronousOperations don't help
+ renameAutocomplete._focused = true;
+ renameAutocomplete.noDebounce = true;
+ renameAutocomplete.text = 'src/test.cpp';
+ await flush();
+ assert.isTrue(queryStub.called);
+ assert.isTrue(element.$.renameDialog.disabled);
- element.$.newPathIronInput.bindValue = 'src/test.newPath';
+ element.$.newPathIronInput.bindValue = 'src/test.newPath';
+ await flush();
- assert.isFalse(element.$.renameDialog.disabled);
- MockInteractions.tap(
- queryAndAssert(element.$.renameDialog, 'gr-button[primary]')
- );
- flush();
+ assert.isFalse(element.$.renameDialog.disabled);
+ MockInteractions.tap(
+ queryAndAssert(element.$.renameDialog, 'gr-button[primary]')
+ );
+ await flush();
+ assert.isTrue(renameStub.called);
- assert.isTrue(renameStub.called);
-
- return renameStub.lastCall.returnValue.then(() => {
- assert.equal(element._path, '');
- assert.isTrue(navStub.called);
- assert.isTrue(closeDialogSpy.called);
- });
- });
+ await renameStub.lastCall.returnValue;
+ assert.equal(element._path, '');
+ assert.isTrue(navStub.called);
+ assert.isTrue(closeDialogSpy.called);
});
- test('rename fails', () => {
+ test('rename fails', async () => {
renameStub.returns(Promise.resolve({ok: false}));
MockInteractions.tap(queryAndAssert(element, '#rename'));
- return showDialogSpy.lastCall.returnValue.then(() => {
- assert.isTrue(element.$.renameDialog.disabled);
- assert.isFalse(queryStub.called);
- // Setup _focused manually - in headless mode Chrome sometimes don't
- // setup focus. flush and/or flushAsynchronousOperations don't help
- renameAutocomplete._focused = true;
- renameAutocomplete.noDebounce = true;
- renameAutocomplete.text = 'src/test.cpp';
- assert.isTrue(queryStub.called);
- assert.isTrue(element.$.renameDialog.disabled);
+ await showDialogSpy.lastCall.returnValue;
+ assert.isTrue(element.$.renameDialog.disabled);
+ assert.isFalse(queryStub.called);
+ // Setup _focused manually - in headless mode Chrome sometimes don't
+ // setup focus. flush and/or flushAsynchronousOperations don't help
+ renameAutocomplete._focused = true;
+ renameAutocomplete.noDebounce = true;
+ renameAutocomplete.text = 'src/test.cpp';
+ await flush();
+ assert.isTrue(queryStub.called);
+ assert.isTrue(element.$.renameDialog.disabled);
- element.$.newPathIronInput.bindValue = 'src/test.newPath';
+ element.$.newPathIronInput.bindValue = 'src/test.newPath';
+ await flush();
- assert.isFalse(element.$.renameDialog.disabled);
- MockInteractions.tap(
- queryAndAssert(element.$.renameDialog, 'gr-button[primary]')
- );
- flush();
+ assert.isFalse(element.$.renameDialog.disabled);
+ MockInteractions.tap(
+ queryAndAssert(element.$.renameDialog, 'gr-button[primary]')
+ );
+ await flush();
- assert.isTrue(renameStub.called);
+ assert.isTrue(renameStub.called);
- return renameStub.lastCall.returnValue.then(() => {
- assert.isFalse(navStub.called);
- assert.isFalse(closeDialogSpy.called);
- });
- });
+ await renameStub.lastCall.returnValue;
+ assert.isFalse(navStub.called);
+ assert.isFalse(closeDialogSpy.called);
});
test('cancel', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
index 74e34a0..97ee39e 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.ts
@@ -15,12 +15,11 @@
* limitations under the License.
*/
import '../gr-button/gr-button';
-import '../../../styles/gr-font-styles';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-dialog_html';
-import {customElement, property, observe} from '@polymer/decorators';
+import {customElement, property, query} from 'lit/decorators';
import {GrButton} from '../gr-button/gr-button';
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {fontStyles} from '../../../styles/gr-font-styles';
declare global {
interface HTMLElementTagNameMap {
@@ -28,18 +27,8 @@
}
}
-export interface GrDialog {
- $: {
- confirm: GrButton;
- };
-}
-
@customElement('gr-dialog')
-export class GrDialog extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrDialog extends LitElement {
/**
* Fired when the confirm button is pressed.
*
@@ -52,33 +41,132 @@
* @event cancel
*/
- @property({type: String})
+ @query('#confirm')
+ confirmButton?: GrButton;
+
+ @property({type: String, attribute: 'confirm-label'})
confirmLabel = 'Confirm';
// Supplying an empty cancel label will hide the button completely.
- @property({type: String})
+ @property({type: String, attribute: 'cancel-label'})
cancelLabel = 'Cancel';
@property({type: Boolean})
disabled = false;
- @property({type: Boolean})
+ @property({type: Boolean, attribute: 'confirm-on-enter'})
confirmOnEnter = false;
- @property({type: String})
+ @property({type: String, attribute: 'confirm-tooltip'})
confirmTooltip?: string;
- override ready() {
- super.ready();
- this._ensureAttribute('role', 'dialog');
+ override firstUpdated(changedProperties: PropertyValues) {
+ super.firstUpdated(changedProperties);
+ if (!this.getAttribute('role')) this.setAttribute('role', 'dialog');
}
- @observe('confirmTooltip')
- _handleConfirmTooltipUpdate(confirmTooltip?: string) {
- if (confirmTooltip) {
- this.$.confirm.setAttribute('has-tooltip', 'true');
+ static override get styles() {
+ return [
+ sharedStyles,
+ fontStyles,
+ css`
+ :host {
+ color: var(--primary-text-color);
+ display: block;
+ max-height: 90vh;
+ overflow: auto;
+ }
+ .container {
+ display: flex;
+ flex-direction: column;
+ max-height: 90vh;
+ padding: var(--spacing-xl);
+ }
+ header {
+ flex-shrink: 0;
+ padding-bottom: var(--spacing-xl);
+ }
+ main {
+ display: flex;
+ flex-shrink: 1;
+ width: 100%;
+ flex: 1;
+ /* IMPORTANT: required for firefox */
+ min-height: 0px;
+ }
+ main .overflow-container {
+ flex: 1;
+ overflow: auto;
+ }
+ footer {
+ display: flex;
+ flex-shrink: 0;
+ justify-content: flex-end;
+ padding-top: var(--spacing-xl);
+ }
+ gr-button {
+ margin-left: var(--spacing-l);
+ }
+ .hidden {
+ display: none;
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ // Note that we are using (e: Event) => this._handleKeyDown because the
+ // tests mock out _handleKeydown so the lookup needs to be dynamic, not
+ // bound statically here.
+ return html`
+ <div
+ class="container"
+ @keydown=${(e: KeyboardEvent) => this._handleKeydown(e)}
+ >
+ <header class="heading-3"><slot name="header"></slot></header>
+ <main>
+ <div class="overflow-container">
+ <slot name="main"></slot>
+ </div>
+ </main>
+ <footer>
+ <slot name="footer"></slot>
+ <gr-button
+ id="cancel"
+ class="${this.cancelLabel.length ? '' : 'hidden'}"
+ link
+ @click=${(e: Event) => this.handleCancelTap(e)}
+ >
+ ${this.cancelLabel}
+ </gr-button>
+ <gr-button
+ id="confirm"
+ link
+ primary
+ @click=${(e: Event) => this._handleConfirm(e)}
+ ?disabled=${this.disabled}
+ title=${this.confirmTooltip ?? ''}
+ >
+ ${this.confirmLabel}
+ </gr-button>
+ </footer>
+ </div>
+ `;
+ }
+
+ override updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('confirmTooltip')) {
+ this.updateTooltip();
+ }
+ }
+
+ private updateTooltip() {
+ const confirmButton = this.confirmButton;
+ if (!confirmButton) return;
+ if (this.confirmTooltip) {
+ confirmButton.setAttribute('has-tooltip', 'true');
} else {
- this.$.confirm.removeAttribute('has-tooltip');
+ confirmButton.removeAttribute('has-tooltip');
}
}
@@ -97,7 +185,7 @@
);
}
- _handleCancelTap(e: Event) {
+ private handleCancelTap(e: Event) {
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(
@@ -115,10 +203,6 @@
}
resetFocus() {
- this.$.confirm.focus();
- }
-
- _computeCancelClass(cancelLabel: string) {
- return cancelLabel.length ? '' : 'hidden';
+ this.confirmButton!.focus();
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_html.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_html.ts
deleted file mode 100644
index a5cf8f1..0000000
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_html.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="gr-font-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="shared-styles">
- :host {
- color: var(--primary-text-color);
- display: block;
- max-height: 90vh;
- overflow: auto;
- }
- .container {
- display: flex;
- flex-direction: column;
- max-height: 90vh;
- padding: var(--spacing-xl);
- }
- header {
- flex-shrink: 0;
- padding-bottom: var(--spacing-xl);
- }
- main {
- display: flex;
- flex-shrink: 1;
- width: 100%;
- flex: 1;
- /* IMPORTANT: required for firefox */
- min-height: 0px;
- }
- main .overflow-container {
- flex: 1;
- overflow: auto;
- }
- footer {
- display: flex;
- flex-shrink: 0;
- justify-content: flex-end;
- padding-top: var(--spacing-xl);
- }
- gr-button {
- margin-left: var(--spacing-l);
- }
- .hidden {
- display: none;
- }
- </style>
- <div class="container" on-keydown="_handleKeydown">
- <header class="heading-3"><slot name="header"></slot></header>
- <main>
- <div class="overflow-container">
- <slot name="main"></slot>
- </div>
- </main>
- <footer>
- <slot name="footer"></slot>
- <gr-button
- id="cancel"
- class$="[[_computeCancelClass(cancelLabel)]]"
- link=""
- on-click="_handleCancelTap"
- >
- [[cancelLabel]]
- </gr-button>
- <gr-button
- id="confirm"
- link=""
- primary=""
- on-click="_handleConfirm"
- disabled="[[disabled]]"
- title$="[[confirmTooltip]]"
- >
- [[confirmLabel]]
- </gr-button>
- </footer>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
index e7b7130..171fc6c 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.ts
@@ -17,6 +17,7 @@
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import '../../../test/common-test-setup-karma';
+import './gr-dialog';
import {GrDialog} from './gr-dialog';
import {isHidden, queryAndAssert} from '../../../test/test-utils';
@@ -25,8 +26,9 @@
suite('gr-dialog tests', () => {
let element: GrDialog;
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await element.updateComplete;
});
test('events', () => {
@@ -42,56 +44,59 @@
assert.equal(cancel.callCount, 1);
});
- test('confirmOnEnter', () => {
+ test('confirmOnEnter', async () => {
element.confirmOnEnter = false;
+ await element.updateComplete;
const handleConfirmStub = sinon.stub(element, '_handleConfirm');
const handleKeydownSpy = sinon.spy(element, '_handleKeydown');
- MockInteractions.pressAndReleaseKeyOn(
+ MockInteractions.keyDownOn(
queryAndAssert(element, 'main'),
13,
null,
'enter'
);
- flush();
+ await flush();
assert.isTrue(handleKeydownSpy.called);
assert.isFalse(handleConfirmStub.called);
element.confirmOnEnter = true;
- MockInteractions.pressAndReleaseKeyOn(
+ await element.updateComplete;
+
+ MockInteractions.keyDownOn(
queryAndAssert(element, 'main'),
13,
null,
'enter'
);
- flush();
+ await flush();
assert.isTrue(handleConfirmStub.called);
});
test('resetFocus', () => {
- const focusStub = sinon.stub(element.$.confirm, 'focus');
+ const focusStub = sinon.stub(element.confirmButton!, 'focus');
element.resetFocus();
assert.isTrue(focusStub.calledOnce);
});
suite('tooltip', () => {
test('tooltip not added by default', () => {
- assert.isNull(element.$.confirm.getAttribute('has-tooltip'));
+ assert.isNull(element.confirmButton!.getAttribute('has-tooltip'));
});
- test('tooltip added if confirm tooltip is passed', () => {
+ test('tooltip added if confirm tooltip is passed', async () => {
element.confirmTooltip = 'confirm tooltip';
- flush();
- assert(element.$.confirm.getAttribute('has-tooltip'));
+ await element.updateComplete;
+ assert(element.confirmButton!.getAttribute('has-tooltip'));
});
});
- test('empty cancel label hides cancel btn', () => {
+ test('empty cancel label hides cancel btn', async () => {
const cancelButton = queryAndAssert(element, '#cancel');
assert.isFalse(isHidden(cancelButton));
element.cancelLabel = '';
- flush();
+ await element.updateComplete;
assert.isTrue(isHidden(cancelButton));
});
diff --git a/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts b/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
index 2fd3fb9..3762d8d 100644
--- a/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
@@ -83,8 +83,8 @@
display: flex;
width: var(--gr-vote-chip-width, 16px);
height: var(--gr-vote-chip-height, 16px);
+ font-size: var(--font-size-small);
justify-content: center;
- margin-right: var(--spacing-s);
padding: 1px;
border-radius: var(--border-radius);
line-height: var(--gr-vote-chip-width, 16px);
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index cde9a45..884afd7 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -122,6 +122,17 @@
return label.all?.filter(x => x._account_id === account._account_id)[0];
}
+export function getAllUniqueApprovals(labelInfo?: LabelInfo) {
+ if (!labelInfo || !isDetailedLabelInfo(labelInfo)) return [];
+ const uniqueApprovals = (labelInfo.all ?? [])
+ .filter(
+ (approvalInfo, index, array) =>
+ index === array.findIndex(other => other.value === approvalInfo.value)
+ )
+ .sort((a, b) => -(a.value ?? 0) + (b.value ?? 0));
+ return uniqueApprovals;
+}
+
export function hasVotes(labelInfo: LabelInfo): boolean {
if (isDetailedLabelInfo(labelInfo)) {
return (labelInfo.all ?? []).some(