Merge "Fix template types of gr-access-section"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index a8d8769..b609643 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -600,8 +600,9 @@
----
As a response, two link:#change-info[ChangeInfo] entities are returned
-that describe information added and removed from the `old` change state.
-Only fields that differ between the change's two states are returned.
+that describe information added and removed from the `old` change state, and
+the two link:#change-info[ChangeInfo] entities that generated the diff are
+returned. Only fields that differ between the change's two states are returned.
.Response
----
@@ -625,9 +626,55 @@
"topic": "new-topic"
},
"removed": {
- "updated": "2013-02-20 12:05:34.111000000",
+ "updated": "2013-02-01 09:59:32.126000000",
"topic": "old-topic"
- }
+ },
+ "old_change_info": {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "attention_set": [],
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "topic": "old-topic",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-01 09:59:32.126000000",
+ "mergeable": true,
+ "insertions": 34,
+ "deletions": 101,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ },
+ "new_change_info": {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "attention_set": [
+ {
+ "account": {
+ "name": "John Doe"
+ },
+ "last_update": "2013-02-21 11:16:36.775000000",
+ "reason": "reviewer or cc replied"
+ }
+ ],
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "topic": "new-topic",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "mergeable": true,
+ "insertions": 34,
+ "deletions": 101,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ },
}
----
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
index ba5e323..ad112d3 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
@@ -63,9 +63,12 @@
*/
public static ChangeInfoDifference getDifference(
ChangeInfo oldChangeInfo, ChangeInfo newChangeInfo) {
- return ChangeInfoDifference.create(
- /* added= */ getAdded(oldChangeInfo, newChangeInfo),
- /* removed= */ getAdded(newChangeInfo, oldChangeInfo));
+ return ChangeInfoDifference.builder()
+ .setOldChangeInfo(oldChangeInfo)
+ .setNewChangeInfo(newChangeInfo)
+ .setAdded(getAdded(oldChangeInfo, newChangeInfo))
+ .setRemoved(getAdded(newChangeInfo, oldChangeInfo))
+ .build();
}
@SuppressWarnings("unchecked") // reflection is used to construct instances of T
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java b/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java
index 269c673..997c3ee 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java
@@ -20,11 +20,29 @@
@AutoValue
public abstract class ChangeInfoDifference {
+ public abstract ChangeInfo oldChangeInfo();
+
+ public abstract ChangeInfo newChangeInfo();
+
public abstract ChangeInfo added();
public abstract ChangeInfo removed();
- public static ChangeInfoDifference create(ChangeInfo added, ChangeInfo removed) {
- return new AutoValue_ChangeInfoDifference(added, removed);
+ public static Builder builder() {
+ return new AutoValue_ChangeInfoDifference.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setOldChangeInfo(ChangeInfo oldChangeInfo);
+
+ public abstract Builder setNewChangeInfo(ChangeInfo newChangeInfo);
+
+ public abstract Builder setAdded(ChangeInfo added);
+
+ public abstract Builder setRemoved(ChangeInfo removed);
+
+ public abstract ChangeInfoDifference build();
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/ListProjects.java b/java/com/google/gerrit/server/restapi/project/ListProjects.java
index c4ae33a..3ef9c8f 100644
--- a/java/com/google/gerrit/server/restapi/project/ListProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/ListProjects.java
@@ -490,7 +490,7 @@
continue;
}
- List<Ref> refs = retrieveBranchRefs(e);
+ List<Ref> refs = retrieveBranchRefs(e, git);
if (!hasValidRef(refs)) {
continue;
}
@@ -578,7 +578,8 @@
}
}
- private List<Ref> retrieveBranchRefs(ProjectState e) throws PermissionBackendException {
+ private List<Ref> retrieveBranchRefs(ProjectState e, Repository git)
+ throws PermissionBackendException {
boolean canReadAllRefs = e.statePermitsRead();
if (canReadAllRefs) {
try {
@@ -588,7 +589,7 @@
}
}
- return getBranchRefs(e.getNameKey(), canReadAllRefs);
+ return getBranchRefs(e.getNameKey(), canReadAllRefs, git);
}
private void addParentProjectInfo(
@@ -708,9 +709,10 @@
stdout.flush();
}
- private List<Ref> getBranchRefs(Project.NameKey projectName, boolean canReadAllRefs) {
+ private List<Ref> getBranchRefs(
+ Project.NameKey projectName, boolean canReadAllRefs, Repository git) {
Ref[] result = new Ref[showBranch.size()];
- try (Repository git = repoManager.openRepository(projectName)) {
+ try {
PermissionBackend.ForProject perm = permissionBackend.user(currentUser).project(projectName);
for (int i = 0; i < showBranch.size(); i++) {
Ref ref = git.findRef(showBranch.get(i));
diff --git a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
index 4352fe8..024e35e 100644
--- a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
+++ b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
@@ -58,6 +58,17 @@
}
@Test
+ public void getDiff_returnsOldAndNewChangeInfos() {
+ ChangeInfo oldChangeInfo = createChangeInfoWithTopic("topic");
+ ChangeInfo newChangeInfo = createChangeInfoWithTopic(oldChangeInfo.topic);
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.oldChangeInfo()).isEqualTo(oldChangeInfo);
+ assertThat(diff.newChangeInfo()).isEqualTo(newChangeInfo);
+ }
+
+ @Test
public void getDiff_givenUnchangedTopic_returnsNullTopics() {
ChangeInfo oldChangeInfo = createChangeInfoWithTopic("topic");
ChangeInfo newChangeInfo = createChangeInfoWithTopic(oldChangeInfo.topic);
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index c8949e6..9ebee9c 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -3698,18 +3698,16 @@
TestRepository<Repo> repo = createProject("repo");
Change change = insert(repo, newChange(repo));
- AssigneeInput ain = new AssigneeInput();
- ain.assignee = user2.toString();
- gApi.changes().id(change.getId().get()).setAssignee(ain);
+ gApi.changes().id(change.getId().get()).addReviewer(user2.toString());
RequestContext adminContext = requestContext.setContext(newRequestContext(user2));
- assertQuery("assignee:self", change);
+ assertQuery("reviewer:self", change);
requestContext.setContext(adminContext);
gApi.accounts().id(user2.get()).setActive(false);
requestContext.setContext(newRequestContext(user2));
- assertQuery("assignee:self", change);
+ assertQuery("reviewer:self", change);
}
@Test
diff --git a/package.json b/package.json
index e1bbf7c..a47ba9f 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"eslint-plugin-html": "^6.1.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsdoc": "^32.3.0",
+ "eslint-plugin-lit": "^1.5.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-regex": "^1.8.0",
diff --git a/plugins/delete-project b/plugins/delete-project
index 3481845..8fe544a 160000
--- a/plugins/delete-project
+++ b/plugins/delete-project
@@ -1 +1 @@
-Subproject commit 3481845462f86959ff4ae4efb726e2bc3e494c3d
+Subproject commit 8fe544ac569efa357ee054257143d8e1d4aa6afd
diff --git a/polygerrit-ui/app/.eslintrc.js b/polygerrit-ui/app/.eslintrc.js
index 247227b..14f9e8c 100644
--- a/polygerrit-ui/app/.eslintrc.js
+++ b/polygerrit-ui/app/.eslintrc.js
@@ -412,11 +412,30 @@
}],
},
},
+ {
+ files: ['*.ts'],
+ excludedFiles: '*_html.ts',
+ rules: {
+ 'lit/attribute-value-entities': 'error',
+ 'lit/binding-positions': 'error',
+ 'lit/no-duplicate-template-bindings': 'error',
+ 'lit/no-invalid-html': 'error',
+ 'lit/no-legacy-template-syntax': 'error',
+ 'lit/no-property-change-update': 'error',
+ 'lit/no-invalid-escape-sequences': 'error',
+ 'lit/no-legacy-imports': 'error',
+ 'lit/no-private-properties': 'error',
+ 'lit/no-useless-template-literals': 'error',
+ 'lit/no-value-attribute': 'error',
+ 'lit/prefer-static-styles': 'error',
+ },
+ },
],
plugins: [
'html',
'jsdoc',
'import',
+ 'lit',
'prettier',
'regex',
],
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 07e9e6f..0552d45 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -105,7 +105,6 @@
"elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_html.ts",
"elements/admin/gr-repo/gr-repo_html.ts",
"elements/admin/gr-rule-editor/gr-rule-editor_html.ts",
- "elements/change-list/gr-change-list-item/gr-change-list-item_html.ts",
"elements/change-list/gr-change-list-view/gr-change-list-view_html.ts",
"elements/change-list/gr-change-list/gr-change-list_html.ts",
"elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts",
@@ -113,9 +112,6 @@
"elements/change/gr-change-metadata/gr-change-metadata_html.ts",
"elements/change/gr-change-requirements/gr-change-requirements_html.ts",
"elements/change/gr-change-view/gr-change-view_html.ts",
- "elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_html.ts",
- "elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_html.ts",
- "elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_html.ts",
"elements/change/gr-file-list-header/gr-file-list-header_html.ts",
"elements/change/gr-file-list/gr-file-list_html.ts",
"elements/change/gr-label-score-row/gr-label-score-row_html.ts",
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index 1453fd0..ee579ff 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -53,30 +53,30 @@
}
/**
+ * Represents a "generic" text range in the code (e.g. text selection)
+ */
+interface TextRange {
+ /** first line of the range (1-based inclusive). */
+ start_line: number;
+ /** first column of the range (in the first line) (1-based inclusive). */
+ start_column: number;
+ /** last line of the range (1-based inclusive). */
+ end_line: number;
+ /** last column of the range (in the end line) (1-based inclusive). */
+ end_column: number;
+}
+
+/**
* Represents a syntax block in a code (e.g. method, function, class, if-else).
*/
export declare interface SyntaxBlock {
/** Name of the block (e.g. name of the method/class)*/
name: string;
- /** Where does this block syntatically starts and ends (line number and column).*/
- range: {
- /** first line of the block (1-based inclusive). */
- start_line: number;
- /**
- * column of the range start inside the first line (e.g. "{" character ending a function/method)
- * (1-based inclusive).
- */
- start_column: number;
- /**
- * last line of the block (1-based inclusive).
- */
- end_line: number;
- /**
- * column of the block end inside the end line (e.g. "}" character ending a function/method)
- * (1-based inclusive).
- */
- end_column: number;
- };
+ /**
+ * Where does this block syntatically starts and ends (line number and
+ * column).
+ */
+ range: TextRange;
/** Sub-blocks of the current syntax block (e.g. methods of a class) */
children: SyntaxBlock[];
}
@@ -210,15 +210,22 @@
}
/**
- * Listens to changes in token highlighting - when a new token starts or stopped being highlighted.
- * Examples:
- * - Token highlighted: ('myFunctionName', 12, [Element]).
- * - Token unhighlighted: (undefined, 0, undefined).
+ * Event details when a token is highlighted.
*/
-export type TokenHighlightedListener = (
- newHighlight: string | undefined,
- newLineNumber: number,
- hoveredElement?: Element
+export declare interface TokenHighlightEventDetails {
+ token: string;
+ element: Element;
+ side: Side;
+ range: TextRange;
+}
+
+/**
+ * Listens to changes in token highlighting - when a new token starts or stopped
+ * being highlighted. undefined is sent if the event is about a clear in
+ * highlighting.
+ */
+export type TokenHighlightListener = (
+ tokenHighlightEvent?: TokenHighlightEventDetails
) => void;
export declare interface ImageDiffPreferences {
diff --git a/polygerrit-ui/app/api/embed.ts b/polygerrit-ui/app/api/embed.ts
index fed724e..520aeec 100644
--- a/polygerrit-ui/app/api/embed.ts
+++ b/polygerrit-ui/app/api/embed.ts
@@ -24,7 +24,7 @@
DiffLayer,
GrAnnotation,
GrDiffCursor,
- TokenHighlightedListener,
+ TokenHighlightListener,
} from './diff';
declare global {
@@ -35,7 +35,7 @@
TokenHighlightLayer: {
new (
container?: HTMLElement,
- listener?: TokenHighlightedListener
+ listener?: TokenHighlightListener
): DiffLayer;
};
};
diff --git a/polygerrit-ui/app/api/popup.ts b/polygerrit-ui/app/api/popup.ts
index 8d81831..d265ee6 100644
--- a/polygerrit-ui/app/api/popup.ts
+++ b/polygerrit-ui/app/api/popup.ts
@@ -17,9 +17,10 @@
export declare interface PopupPluginApi {
/**
- * Opens the popup, inserts it into DOM over current UI.
- * Creates the popup if not previously created. Creates popup content element,
- * if it was provided with constructor.
+ * Opens the popup, inserts it into the DOM over current UI.
+ * Creates the popup if not previously created. Creates and inserts the popup
+ * content element, if a `moduleName` was provided in the constructor.
+ * Otherwise you have to call `appendContent()` when the promise resolves.
*/
open(): Promise<PopupPluginApi>;
@@ -27,4 +28,10 @@
* Hides the popup.
*/
close(): void;
+
+ /**
+ * Appends the given element as a child to the popup. Only call this method
+ * when you have called `popup()` without a `moduleName`.
+ */
+ appendContent(el: HTMLElement): void;
}
diff --git a/polygerrit-ui/app/api/styles.ts b/polygerrit-ui/app/api/styles.ts
index 43149d3..55ac2cc 100644
--- a/polygerrit-ui/app/api/styles.ts
+++ b/polygerrit-ui/app/api/styles.ts
@@ -37,6 +37,7 @@
font: Style;
form: Style;
menuPage: Style;
+ spinner: Style;
subPage: Style;
table: Style;
}
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts
index 8f94b38..645e770 100644
--- a/polygerrit-ui/app/constants/constants.ts
+++ b/polygerrit-ui/app/constants/constants.ts
@@ -306,3 +306,5 @@
}
export const RELOAD_DASHBOARD_INTERVAL_MS = 10 * 1000;
+
+export const SHOWN_ITEMS_COUNT = 25;
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
index cdd2913..b438420 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
@@ -23,7 +23,6 @@
import '../gr-create-group-dialog/gr-create-group-dialog';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-admin-group-list_html';
-import {ListViewMixin} from '../../../mixins/gr-list-view-mixin/gr-list-view-mixin';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {customElement, property, observe, computed} from '@polymer/decorators';
import {AppElementAdminParams} from '../../gr-app-types';
@@ -32,6 +31,7 @@
import {GrCreateGroupDialog} from '../gr-create-group-dialog/gr-create-group-dialog';
import {fireTitleChange} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
+import {SHOWN_ITEMS_COUNT} from '../../../constants/constants';
declare global {
interface HTMLElementTagNameMap {
@@ -46,11 +46,8 @@
};
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ListViewMixin(PolymerElement);
-
@customElement('gr-admin-group-list')
-export class GrAdminGroupList extends base {
+export class GrAdminGroupList extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -82,7 +79,7 @@
* */
@computed('_groups')
get _shownGroups() {
- return this.computeShownItems(this._groups);
+ return this._groups.slice(0, SHOWN_ITEMS_COUNT);
}
@property({type: Number})
@@ -106,8 +103,8 @@
@observe('params')
_paramsChanged(params: AppElementAdminParams) {
this._loading = true;
- this._filter = this.getFilterValue(params);
- this._offset = this.getOffsetValue(params);
+ this._filter = params?.filter ?? '';
+ this._offset = Number(params?.offset ?? 0);
return this._getGroups(this._filter, this._groupsPerPage, this._offset);
}
@@ -184,4 +181,8 @@
_visibleToAll(item: GroupInfo) {
return item.options?.visible_to_all === true ? 'Y' : 'N';
}
+
+ computeLoadingClass(loading: boolean) {
+ return loading ? 'loading' : '';
+ }
}
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
index d40b810..6605350 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.ts
@@ -17,11 +17,9 @@
import '../../../styles/gr-table-styles';
import '../../../styles/shared-styles';
-import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../shared/gr-account-link/gr-account-link';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-group-audit-log_html';
-import {ListViewMixin} from '../../../mixins/gr-list-view-mixin/gr-list-view-mixin';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {customElement, property} from '@polymer/decorators';
import {
@@ -36,11 +34,8 @@
import {appContext} from '../../../services/app-context';
import {ErrorCallback} from '../../../api/rest';
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ListViewMixin(PolymerElement);
-
@customElement('gr-group-audit-log')
-export class GrGroupAuditLog extends base {
+export class GrGroupAuditLog extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -130,6 +125,10 @@
return '';
}
+
+ computeLoadingClass(loading: boolean) {
+ return loading ? 'loading' : '';
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_html.ts b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_html.ts
index 40c2f30..828aa55 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_html.ts
@@ -43,7 +43,7 @@
<template is="dom-repeat" items="[[_auditLog]]">
<tr class="table">
<td class="date">
- <gr-date-formatter has-tooltip="" date-str="[[item.date]]">
+ <gr-date-formatter withTooltip date-str="[[item.date]]">
</gr-date-formatter>
</td>
<td class="type">[[itemType(item.type)]]</td>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
index d26d14c..b91b04b 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.js
@@ -140,7 +140,7 @@
'https://test/site/group/url');
});
- test('save members correctly', () => {
+ test('save members correctly', async () => {
element._groupOwner = true;
const memberName = 'test-admin';
@@ -155,6 +155,7 @@
element.$.groupMemberSearchInput.text = memberName;
element.$.groupMemberSearchInput.value = 1234;
+ await flush();
assert.isFalse(button.hasAttribute('disabled'));
return element._handleSavingGroupMember().then(() => {
@@ -165,7 +166,7 @@
});
});
- test('save included groups correctly', () => {
+ test('save included groups correctly', async () => {
element._groupOwner = true;
const includedGroupName = 'testName';
@@ -179,7 +180,7 @@
element.$.includedGroupSearchInput.text = includedGroupName;
element.$.includedGroupSearchInput.value = 'testId';
-
+ await flush();
assert.isFalse(button.hasAttribute('disabled'));
return element._handleSavingIncludedGroups().then(() => {
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
index e492a15..e390ac5 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.js
@@ -94,6 +94,7 @@
element.$.groupNameInput.text = groupName2;
+ await flush();
assert.isFalse(button.hasAttribute('disabled'));
assert.isTrue(element.$.groupName.classList.contains('edited'));
@@ -122,6 +123,7 @@
element.$.groupOwnerInput.text = 'testId2';
+ await flush();
assert.isFalse(button.hasAttribute('disabled'));
assert.isTrue(element.$.groupOwner.classList.contains('edited'));
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
index 13bd79d..9f51688 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.ts
@@ -19,25 +19,21 @@
import '../../shared/gr-list-view/gr-list-view';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-plugin-list_html';
-import {
- ListViewMixin,
- ListViewParams,
-} from '../../../mixins/gr-list-view-mixin/gr-list-view-mixin';
import {customElement, property} from '@polymer/decorators';
import {PluginInfo} from '../../../types/common';
import {firePageError, fireTitleChange} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
import {ErrorCallback} from '../../../api/rest';
+import {encodeURL, getBaseUrl} from '../../../utils/url-util';
+import {SHOWN_ITEMS_COUNT} from '../../../constants/constants';
+import {ListViewParams} from '../../gr-app-types';
interface PluginInfoWithName extends PluginInfo {
name: string;
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ListViewMixin(PolymerElement);
-
@customElement('gr-plugin-list')
-export class GrPluginList extends base {
+export class GrPluginList extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -85,8 +81,8 @@
_paramsChanged(params: ListViewParams) {
this._loading = true;
- this._filter = this.getFilterValue(params);
- this._offset = this.getOffsetValue(params);
+ this._filter = params?.filter ?? '';
+ this._offset = Number(params?.offset ?? 0);
return this._getPlugins(this._filter, this._pluginsPerPage, this._offset);
}
@@ -114,7 +110,15 @@
}
_computePluginUrl(id: string) {
- return this.getUrl('/', id);
+ return getBaseUrl() + '/' + encodeURL(id, true);
+ }
+
+ computeLoadingClass(loading: boolean) {
+ return loading ? 'loading' : '';
+ }
+
+ computeShownItems(plugins: PluginInfoWithName[]) {
+ return plugins.slice(0, SHOWN_ITEMS_COUNT);
}
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
index 3693845..00c5999 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
@@ -30,7 +30,6 @@
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-detail-list_html';
-import {ListViewMixin} from '../../../mixins/gr-list-view-mixin/gr-list-view-mixin';
import {encodeURL} from '../../../utils/url-util';
import {customElement, property} from '@polymer/decorators';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
@@ -49,6 +48,7 @@
import {firePageError} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
import {ErrorCallback} from '../../../api/rest';
+import {SHOWN_ITEMS_COUNT} from '../../../constants/constants';
const PGP_START = '-----BEGIN PGP SIGNATURE-----';
@@ -60,11 +60,8 @@
};
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ListViewMixin(PolymerElement);
-
@customElement('gr-repo-detail-list')
-export class GrRepoDetailList extends base {
+export class GrRepoDetailList extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -155,8 +152,8 @@
this.detailType = params.detail;
- this._filter = this.getFilterValue(params);
- this._offset = this.getOffsetValue(params);
+ this._filter = params?.filter ?? '';
+ this._offset = Number(params?.offset ?? 0);
if (!this.detailType)
return Promise.reject(new Error('undefined detailType'));
@@ -395,6 +392,14 @@
_computeHideTagger(tagger?: GitPersonInfo) {
return tagger ? '' : 'hide';
}
+
+ computeLoadingClass(loading: boolean) {
+ return loading ? 'loading' : '';
+ }
+
+ computeShownItems(items: BranchInfo[] | TagInfo[]) {
+ return items.slice(0, SHOWN_ITEMS_COUNT);
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_html.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_html.ts
index 4f66f0d..429a6d6 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_html.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_html.ts
@@ -145,10 +145,7 @@
<td class$="tagger [[_hideIfBranch(detailType)]]">
<div class$="tagger [[_computeHideTagger(item.tagger)]]">
<gr-account-link account="[[item.tagger]]"> </gr-account-link>
- (<gr-date-formatter
- has-tooltip=""
- date-str="[[item.tagger.date]]"
- >
+ (<gr-date-formatter withTooltip date-str="[[item.tagger.date]]">
</gr-date-formatter
>)
</div>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
index e87bb0d..beef556 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
@@ -22,16 +22,16 @@
import '../gr-create-repo-dialog/gr-create-repo-dialog';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-list_html';
-import {ListViewMixin} from '../../../mixins/gr-list-view-mixin/gr-list-view-mixin';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {customElement, property, observe, computed} from '@polymer/decorators';
import {AppElementAdminParams} from '../../gr-app-types';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
import {RepoName, ProjectInfoWithName} from '../../../types/common';
import {GrCreateRepoDialog} from '../gr-create-repo-dialog/gr-create-repo-dialog';
-import {ProjectState} from '../../../constants/constants';
+import {ProjectState, SHOWN_ITEMS_COUNT} from '../../../constants/constants';
import {fireTitleChange} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
+import {encodeURL, getBaseUrl} from '../../../utils/url-util';
declare global {
interface HTMLElementTagNameMap {
@@ -46,11 +46,8 @@
};
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ListViewMixin(PolymerElement);
-
@customElement('gr-repo-list')
-export class GrRepoList extends base {
+export class GrRepoList extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -84,7 +81,7 @@
@computed('_repos')
get _shownRepos() {
- return this.computeShownItems(this._repos);
+ return this._repos.slice(0, SHOWN_ITEMS_COUNT);
}
private readonly restApiService = appContext.restApiService;
@@ -99,8 +96,8 @@
@observe('params')
_paramsChanged(params: AppElementAdminParams) {
this._loading = true;
- this._filter = this.getFilterValue(params);
- this._offset = this.getOffsetValue(params);
+ this._filter = params?.filter ?? '';
+ this._offset = Number(params?.offset ?? 0);
return this._getRepos(this._filter, this._reposPerPage, this._offset);
}
@@ -115,7 +112,7 @@
}
_computeRepoUrl(name: string) {
- return this.getUrl(this._path + '/', name);
+ return getBaseUrl() + this._path + '/' + encodeURL(name, true);
}
_computeChangesLink(name: string) {
@@ -185,4 +182,8 @@
const webLinks = repo.web_links;
return webLinks.length ? webLinks : null;
}
+
+ computeLoadingClass(loading: boolean) {
+ return loading ? 'loading' : '';
+ }
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
index 02339cf..6e68ae7 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
@@ -119,15 +119,7 @@
private renderOption(option: PluginOption) {
return html`
<section class="section ${option.info.type}">
- <span class="title">
- <gr-tooltip-content
- has-tooltip="${option.info.description}"
- show-icon="${option.info.description}"
- title="${option.info.description}"
- >
- <span>${option.info.display_name}</span>
- </gr-tooltip-content>
- </span>
+ <span class="title"> ${this.renderOptionTitle(option)} </span>
<span class="value">
${this.renderOptionDetail(option)} ${this.renderInherited(option)}
</span>
@@ -135,6 +127,18 @@
`;
}
+ private renderOptionTitle(option: PluginOption) {
+ const titleName = html`<span>${option.info.display_name}</span>`;
+ if (!option.info.description) return titleName;
+ return html` <gr-tooltip-content
+ has-tooltip
+ show-icon
+ title="${option.info.description}"
+ >
+ ${titleName}
+ </gr-tooltip-content>`;
+ }
+
private renderOptionDetail(option: PluginOption) {
if (option.info.type === ConfigParameterInfoType.ARRAY) {
return html`
@@ -180,7 +184,7 @@
>
<input
is="iron-input"
- value="${option.info.value}"
+ .value="${option.info.value}"
@input=${this._handleStringChange}
data-option-key="${option._key}"
?disabled=${!option.info.editable}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index 313b91f..d160a28 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -28,7 +28,6 @@
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-list-item_html';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {getDisplayName} from '../../../utils/display-name-util';
import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
@@ -76,11 +75,8 @@
// How many reviewers should be shown with an account-label?
const PRIMARY_REVIEWERS_COUNT = 2;
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(PolymerElement);
-
@customElement('gr-change-list-item')
-export class GrChangeListItem extends base {
+export class GrChangeListItem extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -354,10 +350,7 @@
return this._computeAdditionalReviewers(change).length;
}
- _computeAdditionalReviewersTitle(
- change: ChangeInfo | undefined,
- config: ServerInfo
- ) {
+ _computeAdditionalReviewersTitle(change?: ChangeInfo, config?: ServerInfo) {
if (!change || !config) return '';
return this._computeAdditionalReviewers(change)
.map(user => getDisplayName(config, user, true))
@@ -393,13 +386,20 @@
}
_computeWaiting(
- account?: AccountInfo,
- change?: ChangeInfo
+ account?: AccountInfo | null,
+ change?: ChangeInfo | null
): Timestamp | undefined {
if (!account?._account_id || !change?.attention_set) return undefined;
return change?.attention_set[account._account_id]?.last_update;
}
+ _computeIsColumnHidden(columnToCheck?: string, columnsToDisplay?: string[]) {
+ if (!columnsToDisplay || !columnToCheck) {
+ return false;
+ }
+ return !columnsToDisplay.includes(columnToCheck);
+ }
+
toggleReviewed() {
if (!this.change) return;
const newVal = !this.change?.reviewed;
@@ -417,6 +417,11 @@
);
}
+ _formatDate(date: Timestamp | undefined): string | undefined {
+ if (!date) return undefined;
+ return date.toString();
+ }
+
_handleChangeClick() {
// Don't prevent the default and neither stop bubbling. We just want to
// report the click, but then let the browser handle the click on the link.
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 8ee6a3f..f10ffd0 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
@@ -127,7 +127,7 @@
</td>
<td
class="cell subject"
- hidden$="[[isColumnHidden('Subject', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Subject', visibleChangeTableColumns)]]"
>
<a
title$="[[change.subject]]"
@@ -143,7 +143,7 @@
</td>
<td
class="cell status"
- hidden$="[[isColumnHidden('Status', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Status', visibleChangeTableColumns)]]"
>
<template is="dom-repeat" items="[[statuses]]" as="status">
<div class="comma">,</div>
@@ -155,7 +155,7 @@
</td>
<td
class="cell owner"
- hidden$="[[isColumnHidden('Owner', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Owner', visibleChangeTableColumns)]]"
>
<gr-account-link
highlightAttention
@@ -165,7 +165,7 @@
</td>
<td
class="cell assignee"
- hidden$="[[isColumnHidden('Assignee', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Assignee', visibleChangeTableColumns)]]"
>
<template is="dom-if" if="[[change.assignee]]">
<gr-account-link
@@ -179,7 +179,7 @@
</td>
<td
class="cell reviewers"
- hidden$="[[isColumnHidden('Reviewers', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Reviewers', visibleChangeTableColumns)]]"
>
<div>
<template
@@ -204,14 +204,14 @@
</template>
<template is="dom-if" if="[[_computeAdditionalReviewersCount(change)]]">
<span title="[[_computeAdditionalReviewersTitle(change, config)]]">
- +[[_computeAdditionalReviewersCount(change, config)]]
+ +[[_computeAdditionalReviewersCount(change)]]
</span>
</template>
</div>
</td>
<td
class="cell comments"
- hidden$="[[isColumnHidden('Comments', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Comments', visibleChangeTableColumns)]]"
>
<iron-icon
hidden$="[[!change.unresolved_comment_count]]"
@@ -221,22 +221,22 @@
</td>
<td
class="cell repo"
- hidden$="[[isColumnHidden('Repo', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Repo', visibleChangeTableColumns)]]"
>
<a class="fullRepo" href$="[[_computeRepoUrl(change)]]">
- [[_computeRepoDisplay(change)]]
+ [[_computeRepoDisplay(change, false)]]
</a>
<a
class="truncatedRepo"
href$="[[_computeRepoUrl(change)]]"
- title$="[[_computeRepoDisplay(change)]]"
+ title$="[[_computeRepoDisplay(change, false)]]"
>
- [[_computeRepoDisplay(change, 'true')]]
+ [[_computeRepoDisplay(change, true)]]
</a>
</td>
<td
class="cell branch"
- hidden$="[[isColumnHidden('Branch', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Branch', visibleChangeTableColumns)]]"
>
<a href$="[[_computeRepoBranchURL(change)]]"> [[change.branch]] </a>
<template is="dom-if" if="[[change.topic]]">
@@ -250,38 +250,38 @@
</td>
<td
class="cell updated"
- hidden$="[[isColumnHidden('Updated', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Updated', visibleChangeTableColumns)]]"
>
<gr-date-formatter
- has-tooltip=""
- date-str="[[change.updated]]"
+ withTooltip
+ date-str="[[_formatDate(change.updated)]]"
></gr-date-formatter>
</td>
<td
class="cell submitted"
- hidden$="[[isColumnHidden('Submitted', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Submitted', visibleChangeTableColumns)]]"
>
<gr-date-formatter
- has-tooltip=""
- date-str="[[change.submitted]]"
+ withTooltip
+ date-str="[[_formatDate(change.submitted)]]"
></gr-date-formatter>
</td>
<td
class="cell waiting"
- hidden$="[[isColumnHidden('Waiting', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Waiting', visibleChangeTableColumns)]]"
>
<gr-date-formatter
- has-tooltip=""
- force-relative=""
- relative-option-no-ago=""
+ withTooltip
+ forceRelative
+ relativeOptionNoAge
date-str="[[_computeWaiting(account, change)]]"
></gr-date-formatter>
</td>
<td
class="cell size"
- hidden$="[[isColumnHidden('Size', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Size', visibleChangeTableColumns)]]"
>
- <gr-tooltip-content has-tooltip="" title="[[_computeSizeTooltip(change)]]">
+ <gr-tooltip-content has-tooltip title="[[_computeSizeTooltip(change)]]">
<template is="dom-if" if="[[_changeSize]]">
<span>[[_changeSize]]</span>
</template>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
index ac0b929..aa04784 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
@@ -29,6 +29,7 @@
TopicName,
} from '../../../types/common';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {columnNames} from '../gr-change-list/gr-change-list';
import './gr-change-list-item';
import {GrChangeListItem, LabelCategory} from './gr-change-list-item';
@@ -372,7 +373,7 @@
await flush();
- for (const column of element.columnNames) {
+ for (const column of columnNames) {
const elementClass = '.' + column.toLowerCase();
assert.isFalse(
queryAndAssert(element, elementClass).hasAttribute('hidden')
@@ -395,7 +396,7 @@
await flush();
- for (const column of element.columnNames) {
+ for (const column of columnNames) {
const elementClass = '.' + column.toLowerCase();
if (column === 'Repo') {
assert.isTrue(
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index 40674d4..a2a46e0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -24,7 +24,6 @@
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-list_html';
import {appContext} from '../../../services/app-context';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
import {
KeyboardShortcutMixin,
Shortcut,
@@ -57,6 +56,19 @@
const LABEL_PREFIX_INVALID_PROLOG = 'Invalid-Prolog-Rules-Label-Name--';
const MAX_SHORTCUT_CHARS = 5;
+export const columnNames = [
+ 'Subject',
+ 'Status',
+ 'Owner',
+ 'Assignee',
+ 'Reviewers',
+ 'Comments',
+ 'Repo',
+ 'Branch',
+ 'Updated',
+ 'Size',
+];
+
export interface ChangeListSection {
name?: string;
query?: string;
@@ -68,7 +80,7 @@
}
// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(KeyboardShortcutMixin(PolymerElement));
+const base = KeyboardShortcutMixin(PolymerElement);
@customElement('gr-change-list')
export class GrChangeList extends base {
@@ -219,32 +231,44 @@
return;
}
- this.changeTableColumns = this.columnNames;
+ this.changeTableColumns = columnNames;
this.showNumber = false;
- this.visibleChangeTableColumns = this.getEnabledColumns(
- this.columnNames,
- config,
- this.flagsService.enabledExperiments
+ this.visibleChangeTableColumns = this.changeTableColumns.filter(col =>
+ this._isColumnEnabled(col, config, this.flagsService.enabledExperiments)
);
-
if (account && preferences) {
this.showNumber = !!(
preferences && preferences.legacycid_in_change_table
);
if (preferences.change_table && preferences.change_table.length > 0) {
- const prefColumns = this.renameProjectToRepoColumn(
- preferences.change_table
+ const prefColumns = preferences.change_table.map(column =>
+ column === 'Project' ? 'Repo' : column
);
- this.visibleChangeTableColumns = this.getEnabledColumns(
- prefColumns,
- config,
- this.flagsService.enabledExperiments
+ this.visibleChangeTableColumns = prefColumns.filter(col =>
+ this._isColumnEnabled(
+ col,
+ config,
+ this.flagsService.enabledExperiments
+ )
);
}
}
}
/**
+ * Is the column disabled by a server config or experiment? For example the
+ * assignee feature might be disabled and thus the corresponding column is
+ * also disabled.
+ *
+ */
+ _isColumnEnabled(column: string, config: ServerInfo, experiments: string[]) {
+ if (!config || !config.change) return true;
+ if (column === 'Assignee') return !!config.change.enable_assignee;
+ if (column === 'Comments') return experiments.includes('comments-column');
+ return true;
+ }
+
+ /**
* This methods allows us to customize the columns per section.
*
* @param visibleColumns are the columns according to configs and user prefs
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
index 0d2056a..7b226e7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
@@ -269,7 +269,7 @@
});
test('all columns visible', () => {
- for (const column of element.columnNames) {
+ for (const column of element.changeTableColumns) {
const elementClass = '.' + element._lowerCase(column);
assert.isFalse(element.shadowRoot
.querySelector(elementClass).hidden);
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index 456b7cf..9eca3bc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import '../../../styles/gr-a11y-styles';
import '../../../styles/shared-styles';
import '../gr-change-list/gr-change-list';
import '../../shared/gr-button/gr-button';
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
index 484a952..4778890 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_html.ts
@@ -17,6 +17,9 @@
import {html} from '@polymer/polymer/lib/utils/html-tag';
export const htmlTemplate = html`
+ <style include="gr-a11y-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
:host {
display: block;
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
index beadea3..9242a58 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
@@ -15,7 +15,6 @@
* limitations under the License.
*/
-import '../../shared/gr-date-formatter/gr-date-formatter';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {RepoName} from '../../../types/common';
import {WebLinkInfo} from '../../../types/diff';
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_html.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_html.ts
index 614339a..b5f55ca 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_html.ts
@@ -84,24 +84,27 @@
hidden$="[[_shouldHideActions(_topLevelActions.*, _loading)]]"
>
<template is="dom-repeat" items="[[_topLevelPrimaryActions]]" as="action">
- <gr-button
- link=""
+ <gr-tooltip-content
title$="[[action.title]]"
has-tooltip="[[_computeHasTooltip(action.title)]]"
position-below="true"
- data-action-key$="[[action.__key]]"
- class$="[[action.__key]]"
- data-action-type$="[[action.__type]]"
- data-label$="[[action.label]]"
- disabled$="[[_calculateDisabled(action, _hasKnownChainState)]]"
- on-click="_handleActionTap"
>
- <iron-icon
- class$="[[_computeHasIcon(action)]]"
- icon$="gr-icons:[[action.icon]]"
- ></iron-icon>
- [[action.label]]
- </gr-button>
+ <gr-button
+ link=""
+ data-action-key$="[[action.__key]]"
+ class$="[[action.__key]]"
+ data-action-type$="[[action.__type]]"
+ data-label$="[[action.label]]"
+ disabled$="[[_calculateDisabled(action, _hasKnownChainState)]]"
+ on-click="_handleActionTap"
+ >
+ <iron-icon
+ class$="[[_computeHasIcon(action)]]"
+ icon$="gr-icons:[[action.icon]]"
+ ></iron-icon>
+ [[action.label]]
+ </gr-button>
+ </gr-tooltip-content>
</template>
</section>
<section
@@ -113,24 +116,27 @@
items="[[_topLevelSecondaryActions]]"
as="action"
>
- <gr-button
- link=""
+ <gr-tooltip-content
title$="[[action.title]]"
has-tooltip="[[_computeHasTooltip(action.title)]]"
position-below="true"
- data-action-key$="[[action.__key]]"
- class$="[[action.__key]]"
- data-action-type$="[[action.__type]]"
- data-label$="[[action.label]]"
- disabled$="[[_calculateDisabled(action, _hasKnownChainState)]]"
- on-click="_handleActionTap"
>
- <iron-icon
- class$="[[_computeHasIcon(action)]]"
- icon$="gr-icons:[[action.icon]]"
- ></iron-icon>
- [[action.label]]
- </gr-button>
+ <gr-button
+ link=""
+ data-action-key$="[[action.__key]]"
+ class$="[[action.__key]]"
+ data-action-type$="[[action.__type]]"
+ data-label$="[[action.label]]"
+ disabled$="[[_calculateDisabled(action, _hasKnownChainState)]]"
+ on-click="_handleActionTap"
+ >
+ <iron-icon
+ class$="[[_computeHasIcon(action)]]"
+ icon$="gr-icons:[[action.icon]]"
+ ></iron-icon>
+ [[action.label]]
+ </gr-button>
+ </gr-tooltip-content>
</template>
</section>
<gr-button hidden$="[[!_loading]]" disabled=""
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index 04326a6..26e2fb4 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -1094,7 +1094,7 @@
);
tap(abandonButton);
- assert.isUndefined(element.$.confirmAbandonDialog.message);
+ assert.equal(element.$.confirmAbandonDialog.message, '');
});
test('works', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
index c080345..a37daaa 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
@@ -134,9 +134,9 @@
<span class="title">Submitted</span>
<span class="value">
<gr-date-formatter
- has-tooltip=""
+ withTooltip
date-str="[[change.submitted]]"
- show-yesterday=""
+ showYesterday=""
></gr-date-formatter>
</span>
</section>
@@ -154,9 +154,9 @@
</span>
<span class="value">
<gr-date-formatter
- has-tooltip=""
+ withTooltip
date-str="[[change.updated]]"
- show-yesterday=""
+ showYesterday
></gr-date-formatter>
</span>
</section>
@@ -354,8 +354,8 @@
></gr-commit-info>
<gr-tooltip-content
id="parentNotCurrentMessage"
- has-tooltip=""
- show-icon=""
+ has-tooltip
+ show-icon
title$="[[_notCurrentMessage]]"
></gr-tooltip-content>
</li>
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
index 6a31c0a..414784c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.ts
@@ -17,7 +17,6 @@
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-icons/gr-icons';
-import '../../shared/gr-label/gr-label';
import '../../shared/gr-label-info/gr-label-info';
import '../../shared/gr-limited-text/gr-limited-text';
import {PolymerElement} from '@polymer/polymer/polymer-element';
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 58a00f4..3396e13 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -15,6 +15,7 @@
* limitations under the License.
*/
import '@polymer/paper-tabs/paper-tabs';
+import '../../../styles/gr-a11y-styles';
import '../../../styles/shared-styles';
import '../../diff/gr-comment-api/gr-comment-api';
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
@@ -23,7 +24,6 @@
import '../../shared/gr-button/gr-button';
import '../../shared/gr-change-star/gr-change-star';
import '../../shared/gr-change-status/gr-change-status';
-import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../shared/gr-editable-content/gr-editable-content';
import '../../shared/gr-linked-text/gr-linked-text';
import '../../shared/gr-overlay/gr-overlay';
@@ -51,7 +51,7 @@
} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {GrEditConstants} from '../../edit/gr-edit-constants';
import {pluralize} from '../../../utils/string-util';
-import {windowLocationReload} from '../../../utils/dom-util';
+import {windowLocationReload, querySelectorAll} from '../../../utils/dom-util';
import {
GeneratedWebLink,
GerritNav,
@@ -84,6 +84,7 @@
isCc,
isOwner,
isReviewer,
+ isInvolved,
} from '../../../utils/change-util';
import {EventType as PluginEventType} from '../../../api/plugin';
import {customElement, observe, property} from '@polymer/decorators';
@@ -188,6 +189,11 @@
changeComments$,
drafts$,
} from '../../../services/comments/comments-model';
+import {
+ hasAttention,
+ getAddedByReason,
+ getRemovedByReason,
+} from '../../../utils/attention-set-util';
const MIN_LINES_FOR_COMMIT_COLLAPSE = 18;
@@ -579,6 +585,7 @@
[Shortcut.DIFF_RIGHT_AGAINST_LATEST]: '_handleDiffRightAgainstLatest',
[Shortcut.DIFF_BASE_AGAINST_LATEST]: '_handleDiffBaseAgainstLatest',
[Shortcut.OPEN_SUBMIT_DIALOG]: '_handleOpenSubmitDialog',
+ [Shortcut.TOGGLE_ATTENTION_SET]: '_handleToggleAttentionSet',
};
}
@@ -1172,6 +1179,9 @@
_paramsChanged(value: AppElementChangeViewParams) {
if (value.view !== GerritView.CHANGE) {
this._initialLoadComplete = false;
+ querySelectorAll(this, 'gr-overlay').forEach(overlay =>
+ (overlay as GrOverlay).close()
+ );
return;
}
@@ -1523,6 +1533,48 @@
this.$.actions.showSubmitDialog();
}
+ _handleToggleAttentionSet(e: CustomKeyboardEvent) {
+ if (this.shouldSuppressKeyboardShortcut(e)) {
+ return;
+ }
+ if (!this._change || !this._account?._account_id) return;
+ if (!this._loggedIn || !isInvolved(this._change, this._account)) return;
+ if (!this._change.attention_set) this._change.attention_set = {};
+ if (hasAttention(this._account, this._change)) {
+ const reason = getRemovedByReason(this._account, this._serverConfig);
+ if (this._change.attention_set)
+ delete this._change.attention_set[this._account._account_id];
+ fireAlert(this, 'Removing you from the attention set ...');
+ this.restApiService
+ .removeFromAttentionSet(
+ this._change._number,
+ this._account._account_id,
+ reason
+ )
+ .then(() => {
+ fireEvent(this, 'hide-alert');
+ });
+ } else {
+ const reason = getAddedByReason(this._account, this._serverConfig);
+ fireAlert(this, 'Adding you to the attention set ...');
+ this._change.attention_set[this._account._account_id!] = {
+ account: this._account,
+ reason,
+ reason_account: this._account,
+ };
+ this.restApiService
+ .addToAttentionSet(
+ this._change._number,
+ this._account._account_id,
+ reason
+ )
+ .then(() => {
+ fireEvent(this, 'hide-alert');
+ });
+ }
+ this._change = {...this._change};
+ }
+
_handleDiffAgainstBase(e: CustomKeyboardEvent) {
if (this.shouldSuppressKeyboardShortcut(e)) {
return;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index fb80822..d57aca8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -17,6 +17,9 @@
import {html} from '@polymer/polymer/lib/utils/html-tag';
export const htmlTemplate = html`
+ <style include="gr-a11y-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
.container:not(.loading) {
background-color: var(--background-color-tertiary);
@@ -476,7 +479,7 @@
class="commentThreads"
>
<gr-tooltip-content
- has-tooltip=""
+ has-tooltip
title$="[[_computeTotalCommentCounts(_change.unresolved_comment_count, _changeComments)]]"
>
<span>Comments</span></gr-tooltip-content
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index 676e066..6668e15 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -59,6 +59,7 @@
createAccountWithIdNameAndEmail,
createChangeViewChange,
createRelatedChangeAndCommitInfo,
+ createAccountDetailWithId,
} from '../../../test/test-data-generators';
import {ChangeViewPatchRange, GrChangeView} from './gr-change-view';
import {
@@ -366,7 +367,9 @@
},
})
);
- stubRestApi('getAccount').returns(Promise.resolve(undefined));
+ stubRestApi('getAccount').returns(
+ Promise.resolve(createAccountDetailWithId(5))
+ );
stubRestApi('getDiffComments').returns(Promise.resolve({}));
stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
@@ -505,6 +508,39 @@
assert.isNotOk(args[2]);
});
+ test('toggle attention set status', async () => {
+ element._change = {
+ ...createChangeViewChange(),
+ revisions: createRevisions(10),
+ };
+ const addToAttentionSetStub = stubRestApi('addToAttentionSet').returns(
+ Promise.resolve(new Response())
+ );
+
+ const removeFromAttentionSetStub = stubRestApi(
+ 'removeFromAttentionSet'
+ ).returns(Promise.resolve(new Response()));
+ element._patchRange = {
+ basePatchNum: 1 as BasePatchSetNum,
+ patchNum: 3 as RevisionPatchSetNum,
+ };
+ sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
+
+ assert.isNotOk(element._change.attention_set);
+ await element._getLoggedIn();
+ await element.restApiService.getAccount();
+ element._handleToggleAttentionSet(
+ new CustomEvent('') as CustomKeyboardEvent
+ );
+ assert.isTrue(addToAttentionSetStub.called);
+ assert.isFalse(removeFromAttentionSetStub.called);
+
+ element._handleToggleAttentionSet(
+ new CustomEvent('') as CustomKeyboardEvent
+ );
+ assert.isTrue(removeFromAttentionSetStub.called);
+ });
+
suite('plugins adding to file tab', () => {
setup(async () => {
element._changeNum = TEST_NUMERIC_CHANGE_ID;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
index 4fd42b0..07da53f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
@@ -57,7 +57,7 @@
*/
@property({type: String})
- message?: string;
+ message = '';
get keyBindings() {
return {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.js
deleted file mode 100644
index 14d16f5..0000000
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-confirm-abandon-dialog.js';
-
-const basicFixture = fixtureFromElement('gr-confirm-abandon-dialog');
-
-suite('gr-confirm-abandon-dialog tests', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
- });
-
- test('_handleConfirmTap', () => {
- const confirmHandler = sinon.stub();
- element.addEventListener('confirm', confirmHandler);
- sinon.spy(element, '_handleConfirmTap');
- sinon.spy(element, '_confirm');
- element.shadowRoot
- .querySelector('gr-dialog').dispatchEvent(
- new CustomEvent('confirm', {
- composed: true, bubbles: true,
- }));
- assert.isTrue(confirmHandler.called);
- assert.isTrue(confirmHandler.calledOnce);
- assert.isTrue(element._handleConfirmTap.called);
- assert.isTrue(element._confirm.called);
- assert.isTrue(element._confirm.calledOnce);
- });
-
- test('_handleCancelTap', () => {
- const cancelHandler = sinon.stub();
- element.addEventListener('cancel', cancelHandler);
- sinon.spy(element, '_handleCancelTap');
- element.shadowRoot
- .querySelector('gr-dialog').dispatchEvent(
- new CustomEvent('cancel', {
- composed: true, bubbles: true,
- }));
- assert.isTrue(cancelHandler.called);
- assert.isTrue(cancelHandler.calledOnce);
- assert.isTrue(element._handleCancelTap.called);
- assert.isTrue(element._handleCancelTap.calledOnce);
- });
-});
-
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.ts
new file mode 100644
index 0000000..08dda14
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.ts
@@ -0,0 +1,66 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import './gr-confirm-abandon-dialog';
+import {GrConfirmAbandonDialog} from './gr-confirm-abandon-dialog';
+import {queryAndAssert} from '../../../test/test-utils';
+import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
+
+const basicFixture = fixtureFromElement('gr-confirm-abandon-dialog');
+
+suite('gr-confirm-abandon-dialog tests', () => {
+ let element: GrConfirmAbandonDialog;
+
+ setup(() => {
+ element = basicFixture.instantiate();
+ });
+
+ test('_handleConfirmTap', () => {
+ const confirmHandler = sinon.stub();
+ element.addEventListener('confirm', confirmHandler);
+ const confirmTapSpy = sinon.spy(element, '_handleConfirmTap');
+ const confirmSpy = sinon.spy(element, '_confirm');
+ queryAndAssert<GrDialog>(element, 'gr-dialog').dispatchEvent(
+ new CustomEvent('confirm', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ assert.isTrue(confirmHandler.called);
+ assert.isTrue(confirmHandler.calledOnce);
+ assert.isTrue(confirmTapSpy.called);
+ assert.isTrue(confirmSpy.called);
+ assert.isTrue(confirmSpy.calledOnce);
+ });
+
+ test('_handleCancelTap', () => {
+ const cancelHandler = sinon.stub();
+ element.addEventListener('cancel', cancelHandler);
+ const cancelTapSpy = sinon.spy(element, '_handleCancelTap');
+ queryAndAssert<GrDialog>(element, 'gr-dialog').dispatchEvent(
+ new CustomEvent('cancel', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ assert.isTrue(cancelHandler.called);
+ assert.isTrue(cancelHandler.calledOnce);
+ assert.isTrue(cancelTapSpy.called);
+ assert.isTrue(cancelTapSpy.calledOnce);
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index fbf57f3..b061043 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -25,15 +25,14 @@
import {appContext} from '../../../services/app-context';
import {
ChangeInfo,
- BranchInfo,
- RepoName,
BranchName,
+ RepoName,
CommitId,
ChangeInfoId,
} from '../../../types/common';
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
import {customElement, property, observe} from '@polymer/decorators';
-import {AutocompleteSuggestion} from '../../shared/gr-autocomplete/gr-autocomplete';
+import {GrTypedAutocomplete} from '../../shared/gr-autocomplete/gr-autocomplete';
import {HttpMethod, ChangeStatus} from '../../../constants/constants';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {fireEvent} from '../../../utils/event-util';
@@ -68,7 +67,7 @@
export interface GrConfirmCherrypickDialog {
$: {
- branchInput: HTMLElement;
+ branchInput: GrTypedAutocomplete<BranchName>;
};
}
@@ -91,7 +90,7 @@
*/
@property({type: String})
- branch?: BranchName;
+ branch = '' as BranchName;
@property({type: String})
baseCommit?: string;
@@ -106,7 +105,7 @@
commitNum?: CommitId;
@property({type: String})
- message?: string;
+ message = '';
@property({type: String})
project?: RepoName;
@@ -115,7 +114,7 @@
changes: ChangeInfo[] = [];
@property({type: Object})
- _query: (input: string) => Promise<AutocompleteSuggestion[]>;
+ _query?: (input: string) => Promise<{name: BranchName}[]>;
@property({type: Boolean})
_showCherryPickTopic = false;
@@ -249,7 +248,7 @@
cherryPickType: CherryPickType,
duplicateProjectChanges: boolean,
statuses: Statuses,
- branch?: BranchName
+ branch: BranchName
) {
if (!branch) return true;
const duplicateProject =
@@ -398,29 +397,22 @@
this.$.branchInput.focus();
}
- _getProjectBranchesSuggestions(
- input: string
- ): Promise<AutocompleteSuggestion[]> {
- if (!this.project) {
- this.reporting.error(new Error('no project specified'));
- return Promise.resolve([]);
- }
+ _getProjectBranchesSuggestions(input: string) {
+ if (!this.project) return Promise.reject(new Error('Missing project'));
if (input.startsWith('refs/heads/')) {
input = input.substring('refs/heads/'.length);
}
return this.restApiService
.getRepoBranches(input, this.project, SUGGESTIONS_LIMIT)
- .then((response: BranchInfo[] | undefined) => {
+ .then(response => {
if (!response) return [];
- const branches = [];
+ const branches: Array<{name: BranchName}> = [];
for (const branchInfo of response) {
- let branch;
- if (branchInfo.ref.startsWith('refs/heads/')) {
- branch = branchInfo.ref.substring('refs/heads/'.length);
- } else {
- branch = branchInfo.ref;
+ let name: string = branchInfo.ref;
+ if (name.startsWith('refs/heads/')) {
+ name = name.substring('refs/heads/'.length);
}
- branches.push({name: branch});
+ branches.push({name: name as BranchName});
}
return branches;
});
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 ff078f0..1256cc1 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
@@ -115,10 +115,10 @@
current_revision: 'a',
},
];
- setup(() => {
+ setup(async () => {
element.updateChanges(changes);
element._cherryPickType = CHERRY_PICK_TYPES.TOPIC;
- flush();
+ await flush();
});
test('cherry pick topic submit', async () => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
index c196706..8e6521d 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
@@ -21,15 +21,23 @@
import {htmlTemplate} from './gr-confirm-move-dialog_html';
import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {customElement, property} from '@polymer/decorators';
-import {RepoName, BranchName} from '../../../types/common';
-import {AutocompleteSuggestion} from '../../shared/gr-autocomplete/gr-autocomplete';
+import {BranchName, RepoName} from '../../../types/common';
import {appContext} from '../../../services/app-context';
+import {GrTypedAutocomplete} from '../../shared/gr-autocomplete/gr-autocomplete';
const SUGGESTIONS_LIMIT = 15;
// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
const base = KeyboardShortcutMixin(PolymerElement);
+// This is used to make sure 'branch'
+// can be typed as BranchName.
+export interface GrConfirmMoveDialog {
+ $: {
+ branchInput: GrTypedAutocomplete<BranchName>;
+ };
+}
+
@customElement('gr-confirm-move-dialog')
export class GrConfirmMoveDialog extends base {
static get template() {
@@ -49,16 +57,16 @@
*/
@property({type: String})
- branch?: BranchName;
+ branch = '' as BranchName;
@property({type: String})
- message?: string;
+ message = '';
@property({type: String})
project?: RepoName;
@property({type: Object})
- _query: (input: string) => Promise<AutocompleteSuggestion[]>;
+ _query?: (input: string) => Promise<{name: BranchName}[]>;
get keyBindings() {
return {
@@ -95,9 +103,7 @@
);
}
- _getProjectBranchesSuggestions(
- input: string
- ): Promise<AutocompleteSuggestion[]> {
+ _getProjectBranchesSuggestions(input: string) {
if (!this.project) return Promise.reject(new Error('Missing project'));
if (input.startsWith('refs/heads/')) {
input = input.substring('refs/heads/'.length);
@@ -105,21 +111,15 @@
return this.restApiService
.getRepoBranches(input, this.project, SUGGESTIONS_LIMIT)
.then(response => {
- const branches: AutocompleteSuggestion[] = [];
- let branch;
- if (response) {
- response.forEach(value => {
- if (value.ref.startsWith('refs/heads/')) {
- branch = value.ref.substring('refs/heads/'.length);
- } else {
- branch = value.ref;
- }
- branches.push({
- name: branch,
- });
- });
+ if (!response) return [];
+ const branches: Array<{name: BranchName}> = [];
+ for (const branchInfo of response) {
+ let name: string = branchInfo.ref;
+ if (name.startsWith('refs/heads/')) {
+ name = name.substring('refs/heads/'.length);
+ }
+ branches.push({name: name as BranchName});
}
-
return branches;
});
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.ts
similarity index 75%
rename from polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
rename to polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.ts
index 10844f5..ea5d320 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.ts
@@ -15,35 +15,37 @@
* limitations under the License.
*/
-import '../../../test/common-test-setup-karma.js';
-import './gr-confirm-move-dialog.js';
-import {stubRestApi} from '../../../test/test-utils.js';
+import '../../../test/common-test-setup-karma';
+import './gr-confirm-move-dialog';
+import {GrConfirmMoveDialog} from './gr-confirm-move-dialog';
+import {stubRestApi} from '../../../test/test-utils';
+import {BranchName, GitRef, RepoName} from '../../../types/common';
const basicFixture = fixtureFromElement('gr-confirm-move-dialog');
suite('gr-confirm-move-dialog tests', () => {
- let element;
+ let element: GrConfirmMoveDialog;
setup(() => {
- stubRestApi('getRepoBranches').callsFake(input => {
+ stubRestApi('getRepoBranches').callsFake((input: string) => {
if (input.startsWith('test')) {
return Promise.resolve([
{
- ref: 'refs/heads/test-branch',
+ ref: 'refs/heads/test-branch' as GitRef,
revision: '67ebf73496383c6777035e374d2d664009e2aa5c',
can_delete: true,
},
]);
} else {
- return Promise.resolve(undefined);
+ return Promise.resolve([]);
}
});
element = basicFixture.instantiate();
- element.project = 'test-project';
+ element.project = 'test-repo' as RepoName;
});
test('with updated commit message', () => {
- element.branch = 'master';
+ element.branch = 'master' as BranchName;
const myNewMessage = 'updated commit message';
element.message = myNewMessage;
flush();
@@ -52,13 +54,15 @@
test('_getProjectBranchesSuggestions empty', async () => {
const branches = await element._getProjectBranchesSuggestions(
- 'nonexistent');
+ 'nonexistent'
+ );
assert.equal(branches.length, 0);
});
test('_getProjectBranchesSuggestions non-empty', async () => {
const branches = await element._getProjectBranchesSuggestions(
- 'test-branch');
+ 'test-branch'
+ );
assert.equal(branches.length, 1);
assert.equal(branches[0].name, 'test-branch');
});
@@ -68,4 +72,3 @@
assert.equal(branches.length, 0);
});
});
-
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
index bceee27..85f0330 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
@@ -180,50 +180,61 @@
hidden$="[[_computePrefsButtonHidden(diffPrefs, diffPrefsDisabled)]]"
hidden=""
>
- <gr-button
- link=""
- has-tooltip=""
+ <gr-tooltip-content
+ has-tooltip
title="Diff preferences"
- class="prefsButton desktop"
- on-click="_handlePrefsTap"
- ><iron-icon icon="gr-icons:settings"></iron-icon
- ></gr-button>
+ >
+ <gr-button
+ link=""
+
+ class="prefsButton desktop"
+ on-click="_handlePrefsTap"
+ ><iron-icon icon="gr-icons:settings"></iron-icon
+ ></gr-button>
+ </gr-tooltip-content>
</span>
<span class="separator"></span>
</div>
<span class="downloadContainer desktop">
- <gr-button
- link=""
- class="download"
+ <gr-tooltip-content
+ has-tooltip
title="[[createTitle(Shortcut.OPEN_DOWNLOAD_DIALOG,
- ShortcutSection.ACTIONS)]]"
- has-tooltip=""
- on-click="_handleDownloadTap"
- >Download</gr-button
+ ShortcutSection.ACTIONS)]]"
>
+ <gr-button
+ link=""
+ class="download"
+ on-click="_handleDownloadTap"
+ >Download</gr-button
+ >
+ </gr-tooltip-content>
</span>
<template
is="dom-if"
if="[[_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]"
>
- <gr-button
- id="expandBtn"
- link=""
- title="[[createTitle(Shortcut.TOGGLE_ALL_INLINE_DIFFS,
- ShortcutSection.FILE_LIST)]]"
- has-tooltip=""
- on-click="_expandAllDiffs"
- >Expand All</gr-button
- >
- <gr-button
- id="collapseBtn"
- link=""
- on-click="_collapseAllDiffs"
- title="[[createTitle(Shortcut.TOGGLE_ALL_INLINE_DIFFS,
- ShortcutSection.FILE_LIST)]]"
- has-tooltip=""
- >Collapse All</gr-button
- >
+ <gr-tooltip-content
+ has-tooltip
+ title="[[createTitle(Shortcut.TOGGLE_ALL_INLINE_DIFFS,
+ ShortcutSection.FILE_LIST)]]">
+ <gr-button
+ id="expandBtn"
+ link=""
+
+ on-click="_expandAllDiffs"
+ >Expand All</gr-button
+ >
+ <gr-tooltip-content
+ has-tooltip
+ title="[[createTitle(Shortcut.TOGGLE_ALL_INLINE_DIFFS,
+ ShortcutSection.FILE_LIST)]]">
+ <gr-button
+ id="collapseBtn"
+ link=""
+ on-click="_collapseAllDiffs"
+ >Collapse All</gr-button
+ >
+ </gr-tooltip-content>
</template>
<template
is="dom-if"
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
index 08e5e3b..c90cfcc 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
@@ -90,26 +90,26 @@
assert.isFalse(computeSpy.lastCall.returnValue);
});
- test('fileViewActions are properly hidden', () => {
+ test('fileViewActions are properly hidden', async () => {
const actions = element.shadowRoot
.querySelector('.fileViewActions');
assert.equal(getComputedStyle(actions).display, 'none');
element.filesExpanded = FilesExpandedState.SOME;
- flush();
+ await flush();
assert.notEqual(getComputedStyle(actions).display, 'none');
element.filesExpanded = FilesExpandedState.ALL;
- flush();
+ await flush();
assert.notEqual(getComputedStyle(actions).display, 'none');
element.filesExpanded = FilesExpandedState.NONE;
- flush();
+ await flush();
assert.equal(getComputedStyle(actions).display, 'none');
});
- test('expand/collapse buttons are toggled correctly', () => {
+ test('expand/collapse buttons are toggled correctly', async () => {
// Only the expand button should be visible in the initial state when
// NO files are expanded.
element.shownFileCount = 10;
- flush();
+ await flush();
const expandBtn = element.shadowRoot.querySelector('#expandBtn');
const collapseBtn = element.shadowRoot.querySelector('#collapseBtn');
assert.notEqual(getComputedStyle(expandBtn).display, 'none');
@@ -118,19 +118,19 @@
// Both expand and collapse buttons should be visible when SOME files are
// expanded.
element.filesExpanded = FilesExpandedState.SOME;
- flush();
+ await flush();
assert.notEqual(getComputedStyle(expandBtn).display, 'none');
assert.notEqual(getComputedStyle(collapseBtn).display, 'none');
// Only the collapse button should be visible when ALL files are expanded.
element.filesExpanded = FilesExpandedState.ALL;
- flush();
+ await flush();
assert.equal(getComputedStyle(expandBtn).display, 'none');
assert.notEqual(getComputedStyle(collapseBtn).display, 'none');
// Only the expand button should be visible when NO files are expanded.
element.filesExpanded = FilesExpandedState.NONE;
- flush();
+ await flush();
assert.notEqual(getComputedStyle(expandBtn).display, 'none');
assert.equal(getComputedStyle(collapseBtn).display, 'none');
});
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 8d0c203..b8f01de 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import '../../../styles/gr-a11y-styles';
import '../../../styles/shared-styles';
import '../../diff/gr-diff-cursor/gr-diff-cursor';
import '../../diff/gr-diff-host/gr-diff-host';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
index 4d04744..35f01d8 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
@@ -17,6 +17,9 @@
import {html} from '@polymer/polymer/lib/utils/html-tag';
export const htmlTemplate = html`
+ <style include="gr-a11y-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
:host {
display: block;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
index 5def25a..4acf245 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
@@ -17,6 +17,7 @@
import '../../../test/common-test-setup-karma.js';
import '../../diff/gr-comment-api/gr-comment-api.js';
+import '../../shared/gr-date-formatter/gr-date-formatter.js';
import {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
import './gr-file-list.js';
import {createCommentApiMockWithTemplateElement} from '../../../test/mocks/comment-api.js';
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts
index f9d21d9..421cd6e 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts
@@ -53,25 +53,27 @@
);
padding: 0 var(--spacing-m);
}
- gr-button.iron-selected[vote='max'] {
+ gr-tooltip-content.iron-selected > gr-button[vote='max'] {
--button-background-color: var(--vote-color-approved);
}
- gr-button.iron-selected[vote='positive'] {
+ gr-tooltip-content.iron-selected > gr-buttonvote='positive'] {
--button-background-color: var(--vote-color-recommended);
}
- gr-button.iron-selected[vote='min'] {
+ gr-tooltip-content.iron-selected > gr-button[vote='min'] {
--button-background-color: var(--vote-color-rejected);
}
- gr-button.iron-selected[vote='negative'] {
+ gr-tooltip-content.iron-selected > gr-button[vote='negative'] {
--button-background-color: var(--vote-color-disliked);
}
- gr-button.iron-selected[vote='neutral'] {
+ gr-tooltip-content.iron-selected > gr-button[vote='neutral'] {
--button-background-color: var(--vote-color-neutral);
}
- gr-button.iron-selected[vote='positive']::part(paper-button) {
+ gr-tooltip-content.iron-selected
+ > gr-button[vote='positive']::part(paper-button) {
border-color: var(--vote-outline-recommended);
}
- gr-button.iron-selected[vote='negative']::part(paper-button) {
+ gr-tooltip-content.iron-selected
+ > gr-button[vote='negative']::part(paper-button) {
border-color: var(--vote-outline-disliked);
}
.placeholder {
@@ -116,17 +118,20 @@
aria-labelledby="labelName"
>
<template is="dom-repeat" items="[[_items]]" as="value">
- <gr-button
- role="radio"
- vote$="[[_computeVoteAttribute(value, index, _items.length)]]"
- vote-chip
- has-tooltip=""
+ <gr-tooltip-content
+ has-tooltip
+ title$="[[_computeLabelValueTitle(labels, label.name, value)]]"
data-name$="[[label.name]]"
data-value$="[[value]]"
- title$="[[_computeLabelValueTitle(labels, label.name, value)]]"
>
- [[value]]</gr-button
- >
+ <gr-button
+ role="radio"
+ vote="[[_computeVoteAttribute(value, index, _items.length)]]"
+ voteChip
+ >
+ [[value]]
+ </gr-button>
+ </gr-tooltip-content>
</template>
</iron-selector>
<template
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
index e7ff236..34e959b 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
@@ -97,14 +97,14 @@
}
}
- test('label picker', () => {
+ test('label picker', async () => {
const labelsChangedHandler = sinon.stub();
element.addEventListener('labels-changed', labelsChangedHandler);
assert.ok(element.$.labelSelector);
MockInteractions.tap(element.shadowRoot
.querySelector(
- 'gr-button[data-value="-1"]'));
- flush();
+ 'gr-tooltip-content[data-value="-1"] > gr-button'));
+ await flush();
assert.strictEqual(element.selectedValue, '-1');
assert.strictEqual(element.selectedItem
.textContent.trim(), '-1');
@@ -160,26 +160,6 @@
checkAriaCheckedValid();
});
- test('do not display tooltips on touch devices', () => {
- const verifiedBtn = element.shadowRoot
- .querySelector(
- 'iron-selector > gr-button[data-value="-1"]');
-
- // On touch devices, tooltips should not be shown.
- verifiedBtn._isTouchDevice = true;
- verifiedBtn._handleShowTooltip();
- assert.isNotOk(verifiedBtn._tooltip);
- verifiedBtn._handleHideTooltip();
- assert.isNotOk(verifiedBtn._tooltip);
-
- // On other devices, tooltips should be shown.
- verifiedBtn._isTouchDevice = false;
- verifiedBtn._handleShowTooltip();
- assert.isOk(verifiedBtn._tooltip);
- verifiedBtn._handleHideTooltip();
- assert.isNotOk(verifiedBtn._tooltip);
- });
-
test('_computeLabelValue', () => {
assert.strictEqual(element._computeLabelValue(element.labels,
element.permittedLabels,
@@ -209,7 +189,7 @@
'Code-Review'), []);
});
- test('changes in label score are reflected in the DOM', () => {
+ test('changes in label score are reflected in the DOM', async () => {
element.labels = {
'Code-Review': {
values: {
@@ -232,9 +212,10 @@
default_value: 0,
},
};
+ await flush();
const selector = element.$.labelSelector;
element.set('label', {name: 'Verified', value: ' 0'});
- flush();
+ await flush();
assert.strictEqual(selector.selected, ' 0');
assert.strictEqual(
element.$.selectedValueLabel.textContent.trim(), 'No score');
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
index cfecf91..f529464 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
@@ -85,7 +85,7 @@
await flush();
});
- test('get and set label scores', () => {
+ test('get and set label scores', async () => {
for (const label of Object.keys(element.permittedLabels!)) {
const row = queryAndAssert<GrLabelScoreRow>(
element,
@@ -93,6 +93,7 @@
);
row.setSelectedValue('-1');
}
+ await flush();
assert.deepEqual(element.getLabelValues(), {
'Code-Review': -1,
Verified: -1,
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
index ddbd22e..c9680ef 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
@@ -325,8 +325,8 @@
<template is="dom-if" if="[[!message.id]]">
<span class="date">
<gr-date-formatter
- has-tooltip=""
- show-date-and-time=""
+ withTooltip
+ showDateAndTime
date-str="[[message.date]]"
></gr-date-formatter>
</span>
@@ -334,8 +334,8 @@
<template is="dom-if" if="[[message.id]]">
<span class="date" on-click="_handleAnchorClick">
<gr-date-formatter
- has-tooltip=""
- show-date-and-time=""
+ withTooltip
+ showDateAndTime
date-str="[[message.date]]"
></gr-date-formatter>
</span>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
index 933cb821..b8c9319 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.js
@@ -127,7 +127,7 @@
const labelScoreRows = element.getLabelScores().shadowRoot
.querySelector('gr-label-score-row[name="Code-Review"]');
const selectedBtn = labelScoreRows.shadowRoot
- .querySelector('gr-button[data-value="+1"].iron-selected');
+ .querySelector('gr-tooltip-content[data-value="+1"] > gr-button');
assert.isOk(selectedBtn);
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
index b571985..1973fe6 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
@@ -441,23 +441,26 @@
></gr-account-label>
</template>
</template>
- <gr-button
- class="edit-attention-button"
- on-click="_handleAttentionModify"
- disabled="[[_sendDisabled]]"
- link=""
- position-below=""
- data-label="Edit"
- data-action-type="change"
- data-action-key="edit"
- has-tooltip=""
+ <gr-tooltip-content
+ has-tooltip
title="[[_computeAttentionButtonTitle(_sendDisabled)]]"
- role="button"
- tabindex="0"
>
- <iron-icon icon="gr-icons:edit"></iron-icon>
- Modify
- </gr-button>
+ <gr-button
+ class="edit-attention-button"
+ on-click="_handleAttentionModify"
+ disabled="[[_sendDisabled]]"
+ link=""
+ position-below=""
+ data-label="Edit"
+ data-action-type="change"
+ data-action-key="edit"
+ role="button"
+ tabindex="0"
+ >
+ <iron-icon icon="gr-icons:edit"></iron-icon>
+ Modify
+ </gr-button>
+ </gr-tooltip-content>
</div>
<div>
<a
@@ -612,26 +615,32 @@
<!-- Use 'Send' here as the change may only about reviewers / ccs
and when this button is visible, the next button will always
be 'Start review' -->
- <gr-button
- link=""
- disabled="[[_isState(knownLatestState, 'not-latest')]]"
- class="action save"
+ <gr-tooltip-content
has-tooltip=""
- title="[[_saveTooltip]]"
- on-click="_saveClickHandler"
- >Send As WIP</gr-button
+ title$="[[_saveTooltip]]"
>
+ <gr-button
+ link=""
+ disabled="[[_isState(knownLatestState, 'not-latest')]]"
+ class="action save"
+ on-click="_saveClickHandler"
+ >Send As WIP</gr-button
+ >
+ </gr-tooltip-content>
</template>
- <gr-button
- id="sendButton"
- primary=""
- disabled="[[_sendDisabled]]"
- class="action send"
+ <gr-tooltip-content
has-tooltip=""
title$="[[_computeSendButtonTooltip(canBeStarted, _commentEditing)]]"
- on-click="_sendTapHandler"
- >[[_sendButtonLabel]]</gr-button
>
+ <gr-button
+ id="sendButton"
+ primary=""
+ disabled="[[_sendDisabled]]"
+ class="action send"
+ on-click="_sendTapHandler"
+ >[[_sendButtonLabel]]
+ </gr-button>
+ </gr-tooltip-content>
</div>
</section>
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
index e3bbd9e..e57ffc7 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -116,7 +116,7 @@
return {id: `${lastId++}` as GroupId};
};
- setup(() => {
+ setup(async () => {
changeNum = 42 as NumericChangeId;
patchNum = 1 as PatchSetNum;
@@ -168,7 +168,7 @@
// .returns(Promise.resolve({isLatest: true}));
// Allow the elements created by dom-repeat to be stamped.
- flush();
+ await flush();
});
function stubSaveReview(
@@ -216,6 +216,7 @@
// which the dom-repeat elements are stamped.
await flush();
tap(queryAndAssert(element, '.send'));
+ await flush();
const review = await saveReviewPromise;
assert.deepEqual(review, {
@@ -1063,6 +1064,7 @@
const label = 'Verified';
const value = '+1';
element.setLabelValue(label, value);
+ await flush();
const labels = (
queryAndAssert(element, '#labelScores') as GrLabelScores
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 fa590a3..fd85117 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
@@ -16,7 +16,7 @@
*/
import '../gr-submit-requirement-hovercard/gr-submit-requirement-hovercard';
import {LitElement, css, html} from 'lit';
-import {customElement, property} from 'lit/decorators';
+import {customElement, property, state} from 'lit/decorators';
import {ParsedChangeInfo} from '../../../types/types';
import {
AccountInfo,
@@ -32,7 +32,14 @@
iconForStatus,
} from '../../../utils/label-util';
import {fontStyles} from '../../../styles/gr-font-styles';
-import {charsOnly} from '../../../utils/string-util';
+import {charsOnly, pluralize} from '../../../utils/string-util';
+import {subscribe} from '../../lit/subscription-controller';
+import {
+ allRunsLatestPatchsetLatestAttempt$,
+ CheckRun,
+} from '../../../services/checks/checks-model';
+import {getResultsOf, hasResultsOf} from '../../../services/checks/checks-util';
+import {Category} from '../../../api/checks';
@customElement('gr-submit-requirements')
export class GrSubmitRequirements extends LitElement {
@@ -45,6 +52,9 @@
@property({type: Boolean})
mutable?: boolean;
+ @state()
+ runs: CheckRun[] = [];
+
static override get styles() {
return [
fontStyles,
@@ -95,10 +105,25 @@
td {
padding: var(--spacing-s);
}
+ .votes-cell {
+ display: flex;
+ }
+ .check-error {
+ margin-right: var(--spacing-l);
+ }
+ .check-error iron-icon {
+ color: var(--error-foreground);
+ vertical-align: top;
+ }
`,
];
}
+ constructor() {
+ super();
+ subscribe(this, allRunsLatestPatchsetLatestAttempt$, x => (this.runs = x));
+ }
+
override render() {
const submit_requirements = (this.change?.submit_requirements ?? []).filter(
req => req.status !== SubmitRequirementStatus.NOT_APPLICABLE
@@ -130,7 +155,12 @@
.text="${requirement.name}"
></gr-limited-text>
</td>
- <td>${this.renderVotes(requirement)}</td>
+ <td>
+ <div class="votes-cell">
+ ${this.renderVotes(requirement)}
+ ${this.renderChecks(requirement)}
+ </div>
+ </td>
</tr>`
)}
</tbody>
@@ -199,6 +229,28 @@
);
}
+ renderChecks(requirement: SubmitRequirementResultInfo) {
+ const requirementLabels = extractAssociatedLabels(requirement);
+ const requirementRuns = this.runs
+ .filter(run => hasResultsOf(run, Category.ERROR))
+ .filter(
+ run => run.labelName && requirementLabels.includes(run.labelName)
+ );
+ const runsCount = requirementRuns.reduce(
+ (sum, run) => sum + getResultsOf(run, Category.ERROR).length,
+ 0
+ );
+ if (runsCount > 0) {
+ return html`<span class="check-error"
+ ><iron-icon icon="gr-icons:error"></iron-icon>${pluralize(
+ runsCount,
+ 'error'
+ )}</span
+ >`;
+ }
+ return;
+ }
+
renderTriggerVotes(submitReqs: SubmitRequirementResultInfo[]) {
const labels = this.change?.labels ?? {};
const allLabels = Object.keys(labels);
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 90f7215..68c7957 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -279,11 +279,10 @@
];
}
- override update(changedProperties: PropertyValues) {
+ override updated(changedProperties: PropertyValues) {
if (changedProperties.has('result')) {
this.isExpandable = !!this.result?.summary && !!this.result?.message;
}
- super.update(changedProperties);
}
override focus() {
@@ -978,6 +977,7 @@
override render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const style = html`<style>
.headerTopRow .right .goToLatest gr-button {
--gr-button: {
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index 28354d2..76fa353 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -303,9 +303,6 @@
renderStatusLink() {
const link = this.run.statusLink;
if (!link) return;
- // For COMPLETED we think that the status link are too much clutter.
- // That could be re-considered.
- if (this.run.status !== RunStatus.RUNNING) return;
return html`
<a href="${link}" target="_blank" @click="${this.onLinkClick}"
><iron-icon
@@ -604,42 +601,48 @@
@click="${() => fireRunSelectionReset(this)}"
>Unselect All</gr-button
>
- <gr-button
- class="font-normal"
- link
+ <gr-tooltip-content
title="${runButtonDisabled
? 'Disabled. Unselect checks without a "Run" action to enable the button.'
: ''}"
- has-tooltip="${runButtonDisabled}"
- ?disabled="${runButtonDisabled}"
- @click="${() => {
- actions.forEach(action => this.checksService.triggerAction(action));
- }}"
- >Run Selected</gr-button
+ ?has-tooltip=${runButtonDisabled}
>
+ <gr-button
+ class="font-normal"
+ link
+ ?disabled=${runButtonDisabled}
+ @click="${() => {
+ actions.forEach(action => this.checksService.triggerAction(action));
+ }}"
+ >Run Selected</gr-button
+ >
+ </gr-tooltip-content>
`;
}
private renderCollapseButton() {
return html`
- <gr-button
- link
- class="expandButton"
- role="switch"
- ?aria-checked="${this.collapsed}"
- aria-label="${this.collapsed
- ? 'Expand runs panel'
- : 'Collapse runs panel'}"
- has-tooltip="true"
+ <gr-tooltip-content
+ has-tooltip
title="${this.collapsed ? 'Expand runs panel' : 'Collapse runs panel'}"
- @click="${() => (this.collapsed = !this.collapsed)}"
- ><iron-icon
- class="expandIcon"
- icon="${this.collapsed
- ? 'gr-icons:chevron-right'
- : 'gr-icons:chevron-left'}"
- ></iron-icon>
- </gr-button>
+ >
+ <gr-button
+ link
+ class="expandButton"
+ role="switch"
+ ?aria-checked="${this.collapsed}"
+ aria-label="${this.collapsed
+ ? 'Expand runs panel'
+ : 'Collapse runs panel'}"
+ @click="${() => (this.collapsed = !this.collapsed)}"
+ ><iron-icon
+ class="expandIcon"
+ icon="${this.collapsed
+ ? 'gr-icons:chevron-right'
+ : 'gr-icons:chevron-left'}"
+ ></iron-icon>
+ </gr-button>
+ </gr-tooltip-content>
`;
}
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
index 1517d36..c2066d7 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.ts
@@ -95,6 +95,7 @@
override render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const customStyle = html`
<style>
gr-dropdown {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index 4a6f009..8e70eb9 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -177,8 +177,8 @@
CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
- // Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>][/].
- CHANGE_LEGACY: /^\/c\/(\d+)\/?(((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
+ // Matches /c/<changeNum>/[*][/].
+ CHANGE_LEGACY: /^\/c\/(\d+)\/(.*)$/,
CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
// Matches
@@ -210,10 +210,6 @@
// Matches /c/<project>/+/<changeNum>/[<patchNum|edit>]/<path>,edit[#lineNum]
DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit(#\d+)?$/,
- // Matches non-project-relative
- // /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
- DIFF_LEGACY: /^\/c\/(\d+)\/((-?\d+|edit)(\.\.(\d+|edit))?)\/(.+)/,
-
// Matches diff routes using @\d+ to specify a file name (whether or not
// the project name is included).
// eslint-disable-next-line max-len
@@ -287,15 +283,6 @@
type QueryStringItem = [string, string]; // [key, value]
-type GenerateUrlLegacyChangeViewParameters = Omit<
- GenerateUrlChangeViewParameters,
- 'project'
->;
-type GenerateUrlLegacyDiffViewParameters = Omit<
- GenerateUrlDiffViewParameters,
- 'project'
->;
-
interface PatchRangeParams {
patchNum?: PatchSetNum;
basePatchNum?: BasePatchSetNum;
@@ -679,37 +666,6 @@
}
/**
- * Given a set of params without a project, gets the project from the rest
- * API project lookup and then sets the app params.
- */
- _normalizeLegacyRouteParams(
- params: Readonly<
- | GenerateUrlLegacyChangeViewParameters
- | GenerateUrlLegacyDiffViewParameters
- >
- ) {
- if (!params.changeNum) {
- return Promise.resolve();
- }
-
- return this.restApiService
- .getFromProjectLookup(params.changeNum)
- .then(project => {
- // Show a 404 and terminate if the lookup request failed. Attempting
- // to redirect after failing to get the project loops infinitely.
- if (!project) {
- this._show404();
- return;
- }
- const updatedParams:
- | GenerateUrlChangeViewParameters
- | GenerateUrlDiffViewParameters = {...params, project};
- this._normalizePatchRangeParams(updatedParams);
- this._redirect(this._generateUrl(updatedParams));
- });
- }
-
- /**
* Normalizes the params object, and determines if the URL needs to be
* modified to fit the proper schema.
*
@@ -1100,8 +1056,6 @@
this._mapRoute(RoutePattern.CHANGE_LEGACY, '_handleChangeLegacyRoute');
- this._mapRoute(RoutePattern.DIFF_LEGACY, '_handleDiffLegacyRoute');
-
this._mapRoute(RoutePattern.AGREEMENTS, '_handleAgreementsRoute', true);
this._mapRoute(
@@ -1666,41 +1620,26 @@
}
_handleChangeLegacyRoute(ctx: PageContextWithQueryMap) {
- // Parameter order is based on the regex group number matched.
- const params: GenerateUrlLegacyChangeViewParameters = {
- changeNum: Number(ctx.params[0]) as NumericChangeId,
- basePatchNum: convertToPatchSetNum(ctx.params[3]) as BasePatchSetNum,
- patchNum: convertToPatchSetNum(ctx.params[5]),
- view: GerritView.CHANGE,
- querystring: ctx.querystring,
- };
-
- this._normalizeLegacyRouteParams(params);
+ const changeNum = Number(ctx.params[0]) as NumericChangeId;
+ if (!changeNum) {
+ this._show404();
+ return;
+ }
+ this.restApiService.getFromProjectLookup(changeNum).then(project => {
+ // Show a 404 and terminate if the lookup request failed. Attempting
+ // to redirect after failing to get the project loops infinitely.
+ if (!project) {
+ this._show404();
+ return;
+ }
+ this._redirect(`/c/${project}/+/${changeNum}/${ctx.params[1]}`);
+ });
}
_handleLegacyLinenum(ctx: PageContextWithQueryMap) {
this._redirect(ctx.path.replace(LEGACY_LINENUM_PATTERN, '#$1'));
}
- _handleDiffLegacyRoute(ctx: PageContextWithQueryMap) {
- // Parameter order is based on the regex group number matched.
- const params: GenerateUrlLegacyDiffViewParameters = {
- changeNum: Number(ctx.params[0]) as NumericChangeId,
- basePatchNum: convertToPatchSetNum(ctx.params[2]) as BasePatchSetNum,
- patchNum: convertToPatchSetNum(ctx.params[4]),
- path: ctx.params[5],
- view: GerritView.DIFF,
- };
-
- const address = this._parseLineAddress(ctx.hash);
- if (address) {
- params.leftSide = address.leftSide;
- params.lineNum = address.lineNum;
- }
-
- this._normalizeLegacyRouteParams(params);
- }
-
_handleDiffEditRoute(ctx: PageContextWithQueryMap) {
// Parameter order is based on the regex group number matched.
const project = ctx.params[0] as RepoName;
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
index 4c7855b..b91bf0c 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
@@ -192,7 +192,6 @@
'_handleDiffRoute',
'_handleDefaultRoute',
'_handleChangeLegacyRoute',
- '_handleDiffLegacyRoute',
'_handleDocumentationRedirectRoute',
'_handleDocumentationSearchRoute',
'_handleDocumentationSearchRedirectRoute',
@@ -536,66 +535,6 @@
});
suite('param normalization', () => {
- let projectLookupStub;
- let generateUrlStub;
-
- setup(() => {
- projectLookupStub = stubRestApi('getFromProjectLookup');
- generateUrlStub = sinon.stub(element, '_generateUrl');
- });
-
- suite('_normalizeLegacyRouteParams', () => {
- let rangeStub;
- let redirectStub;
- let show404Stub;
-
- setup(() => {
- rangeStub = sinon.stub(element, '_normalizePatchRangeParams')
- .returns(Promise.resolve());
- redirectStub = sinon.stub(element, '_redirect');
- show404Stub = sinon.stub(element, '_show404');
- });
-
- test('w/o changeNum', () => {
- projectLookupStub.returns(Promise.resolve('foo/bar'));
- const params = {};
- return element._normalizeLegacyRouteParams(params).then(() => {
- assert.isFalse(generateUrlStub.calledOnce);
- assert.isFalse(projectLookupStub.called);
- assert.isFalse(rangeStub.called);
- assert.isFalse(redirectStub.called);
- assert.isFalse(show404Stub.called);
- });
- });
-
- test('w/ changeNum', () => {
- projectLookupStub.returns(Promise.resolve('foo/bar'));
- const params = {changeNum: 1234};
-
- return element._normalizeLegacyRouteParams(params).then(() => {
- assert.isTrue(generateUrlStub.calledOnce);
- const updatedParams = generateUrlStub.lastCall.args[0];
- assert.isTrue(projectLookupStub.called);
- assert.isTrue(rangeStub.called);
- assert.equal(updatedParams.project, 'foo/bar');
- assert.isTrue(redirectStub.calledOnce);
- assert.isFalse(show404Stub.called);
- });
- });
-
- test('halts on project lookup failure', () => {
- projectLookupStub.returns(Promise.resolve(undefined));
- const params = {changeNum: 1234};
- return element._normalizeLegacyRouteParams(params).then(() => {
- assert.isFalse(generateUrlStub.calledOnce);
- assert.isTrue(projectLookupStub.called);
- assert.isFalse(rangeStub.called);
- assert.isFalse(redirectStub.called);
- assert.isTrue(show404Stub.calledOnce);
- });
- });
- });
-
suite('_normalizePatchRangeParams', () => {
test('range n..n normalizes to n', () => {
const params = {basePatchNum: 4, patchNum: 4};
@@ -1367,58 +1306,19 @@
assert.isTrue(redirectStub.calledWithExactly('/c/12345'));
});
- test('_handleChangeLegacyRoute', () => {
- const normalizeRouteStub = sinon.stub(element,
- '_normalizeLegacyRouteParams');
+ test('_handleChangeLegacyRoute', async () => {
+ stubRestApi('getFromProjectLookup').returns(Promise.resolve('project'));
const ctx = {
params: [
1234, // 0 Change number
- null, // 1 Unused
- null, // 2 Unused
- 6, // 3 Base patch number
- null, // 4 Unused
- 9, // 5 Patch number
+ 'comment/6789',
],
querystring: '',
};
element._handleChangeLegacyRoute(ctx);
- assert.isTrue(normalizeRouteStub.calledOnce);
- assert.deepEqual(normalizeRouteStub.lastCall.args[0], {
- changeNum: 1234,
- basePatchNum: 6,
- patchNum: 9,
- view: GerritView.CHANGE,
- querystring: '',
- });
- });
-
- test('_handleDiffLegacyRoute', () => {
- const normalizeRouteStub = sinon.stub(element,
- '_normalizeLegacyRouteParams');
- const ctx = {
- params: [
- 1234, // 0 Change number
- null, // 1 Unused
- 3, // 2 Base patch number
- null, // 3 Unused
- 8, // 4 Patch number
- 'foo/bar', // 5 Diff path
- ],
- path: '/c/1234/3..8/foo/bar',
- hash: 'b123',
- };
- element._handleDiffLegacyRoute(ctx);
- assert.isFalse(redirectStub.called);
- assert.isTrue(normalizeRouteStub.calledOnce);
- assert.deepEqual(normalizeRouteStub.lastCall.args[0], {
- changeNum: 1234,
- basePatchNum: 3,
- patchNum: 8,
- view: GerritView.DIFF,
- path: 'foo/bar',
- lineNum: 123,
- leftSide: true,
- });
+ await flush();
+ assert.isTrue(redirectStub.calledWithExactly('/c/project/+/1234' +
+ '/comment/6789'));
});
test('_handleLegacyLinenum w/ @321', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
index d3d7615..94d37f5 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog_test.ts
@@ -186,6 +186,7 @@
})
);
element._isApplyFixLoading = true;
+ await flush();
const button = getConfirmButton();
assert.isTrue(button.hasAttribute('disabled'));
assert.equal(button.getAttribute('title'), '');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
index 480e26c..de7d007 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
@@ -15,9 +15,17 @@
* limitations under the License.
*/
import {DiffLayer, DiffLayerListener} from '../../../types/types';
-import {GrDiffLine, Side, TokenHighlightedListener} from '../../../api/diff';
+import {GrDiffLine, Side, TokenHighlightListener} from '../../../api/diff';
+import {assertIsDefined} from '../../../utils/common-util';
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
import {debounce, DelayedTask} from '../../../utils/async-util';
+
+import {
+ getLineElByChild,
+ getSideByLineEl,
+ getPreviousContentNodes,
+} from '../gr-diff/gr-diff-utils';
+
import {
getLineNumberByChild,
lineNumberToNumber,
@@ -66,7 +74,7 @@
private currentHighlight?: string;
/** Trigger when a new token starts or stoped being highlighted.*/
- private readonly tokenHighlightedListener?: TokenHighlightedListener;
+ private readonly tokenHighlightListener?: TokenHighlightListener;
/**
* The line of the currently highlighted token. We store this in order to
@@ -100,9 +108,9 @@
constructor(
container: HTMLElement = document.documentElement,
- tokenHighlightedListener?: TokenHighlightedListener
+ tokenHighlightListener?: TokenHighlightListener
) {
- this.tokenHighlightedListener = tokenHighlightedListener;
+ this.tokenHighlightListener = tokenHighlightListener;
container.addEventListener('click', e => {
this.handleContainerClick(e);
});
@@ -260,18 +268,42 @@
const oldLineNumber = this.currentHighlightLineNumber;
this.currentHighlight = newHighlight;
this.currentHighlightLineNumber = newLineNumber;
-
- if (this.tokenHighlightedListener) {
- this.tokenHighlightedListener(
- newHighlight,
- newLineNumber,
- newHoveredElement
- );
- }
+ this.triggerTokenHighlightEvent(
+ newHighlight,
+ newLineNumber,
+ newHoveredElement
+ );
this.notifyForToken(oldHighlight, oldLineNumber);
this.notifyForToken(newHighlight, newLineNumber);
}
+ triggerTokenHighlightEvent(
+ token: string | undefined,
+ line: number,
+ element: Element | undefined
+ ) {
+ if (!this.tokenHighlightListener) {
+ return;
+ }
+ if (!token || !element) {
+ this.tokenHighlightListener(undefined);
+ return;
+ }
+ const previousTextLength = getPreviousContentNodes(element)
+ .map(sib => sib.textContent!.length)
+ .reduce((partial_sum, a) => partial_sum + a, 0);
+ const lineEl = getLineElByChild(element);
+ assertIsDefined(lineEl, 'Line element should be found!');
+ const side = getSideByLineEl(lineEl);
+ const range = {
+ start_line: line,
+ start_column: previousTextLength + 1, // 1-based inclusive
+ end_line: line,
+ end_column: previousTextLength + token.length, // 1-based inclusive
+ };
+ this.tokenHighlightListener({token, element, side, range});
+ }
+
getSortedLinesForSide(
lineMapping: Map<string, Set<number>>,
token: string | undefined,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
index 4f44665..2993d35 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
@@ -17,7 +17,7 @@
import '../../../test/common-test-setup-karma';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
-import {Side} from '../../../api/diff';
+import {Side, TokenHighlightEventDetails} from '../../../api/diff';
import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line.js';
import {HOVER_DELAY_MS, TokenHighlightLayer} from './token-highlight-layer';
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
@@ -66,14 +66,12 @@
let container: HTMLElement;
let listener: MockListener;
let highlighter: TokenHighlightLayer;
- let tokenHighlightingCalls: any[] = [];
+ let tokenHighlightingCalls: {details?: TokenHighlightEventDetails}[] = [];
- function tokenHighlightedListener(
- newHighlight: string | undefined,
- newLineNumber: number,
- hoveredElement?: Element
+ function tokenHighlightListener(
+ highlightDetails?: TokenHighlightEventDetails
) {
- tokenHighlightingCalls.push({newHighlight, newLineNumber, hoveredElement});
+ tokenHighlightingCalls.push({details: highlightDetails});
}
setup(async () => {
@@ -81,7 +79,7 @@
tokenHighlightingCalls = [];
container = document.createElement('div');
document.body.appendChild(container);
- highlighter = new TokenHighlightLayer(container, tokenHighlightedListener);
+ highlighter = new TokenHighlightLayer(container, tokenHighlightListener);
highlighter.addListener((...args) => listener.notify(...args));
});
@@ -107,10 +105,13 @@
const lineId = createLineId();
const template = html`
<div class="line">
- <div data-value=${line} class="lineNum"></div>
- <div id=${lineId} class="line-content">${text}</div>
+ <div data-value=${line} class="lineNum right"></div>
+ <div class="content">
+ <div id=${lineId} class="contentText">${text}</div>
+ </div>
</div>
`;
+
const div = document.createElement('div');
render(template, div);
container.appendChild(div);
@@ -277,19 +278,16 @@
assert.equal(tokenHighlightingCalls.length, 0);
clock.tick(HOVER_DELAY_MS);
assert.equal(tokenHighlightingCalls.length, 1);
- assert.deepEqual(tokenHighlightingCalls[0], {
- newHighlight: 'words',
- newLineNumber: 1,
- hoveredElement: words1,
+ assert.deepEqual(tokenHighlightingCalls[0].details, {
+ token: 'words',
+ side: Side.RIGHT,
+ element: words1,
+ range: {start_line: 1, start_column: 5, end_line: 1, end_column: 9},
});
MockInteractions.click(container);
assert.equal(tokenHighlightingCalls.length, 2);
- assert.deepEqual(tokenHighlightingCalls[1], {
- newHighlight: undefined,
- newLineNumber: 0,
- hoveredElement: undefined,
- });
+ assert.deepEqual(tokenHighlightingCalls[1].details, undefined);
});
test('clicking clears highlight', async () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
index ba1abd0..496d6bf 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -579,6 +579,7 @@
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const customStyle = html`
<style>
paper-item {
@@ -648,7 +649,9 @@
// We don't want property changes in updateSizes() to trigger infinite update
// loops, so we perform this in update() instead of updated().
override update(changedProperties: PropertyValues) {
+ // eslint-disable-next-line lit/no-property-change-update
if (!this.baseUrl) this.baseSelected = false;
+ // eslint-disable-next-line lit/no-property-change-update
if (!this.revisionUrl) this.baseSelected = true;
this.updateSizes();
super.update(changedProperties);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
index 4e2b6a1..8a6d95d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
@@ -30,28 +30,34 @@
width: 1.3rem;
}
</style>
- <gr-button
- id="sideBySideBtn"
- link=""
+ <gr-tooltip-content
has-tooltip=""
- position-below="[[showTooltipBelow]]"
- class$="[[_computeSideBySideSelected(mode)]]"
title="Side-by-side diff"
- aria-pressed$="[[isSideBySideSelected(mode)]]"
- on-click="_handleSideBySideTap"
+ position-below="[[showTooltipBelow]]"
>
- <iron-icon icon="gr-icons:side-by-side"></iron-icon>
- </gr-button>
- <gr-button
- id="unifiedBtn"
- link=""
+ <gr-button
+ id="sideBySideBtn"
+ link=""
+ class$="[[_computeSideBySideSelected(mode)]]"
+ aria-pressed$="[[isSideBySideSelected(mode)]]"
+ on-click="_handleSideBySideTap"
+ >
+ <iron-icon icon="gr-icons:side-by-side"></iron-icon>
+ </gr-button>
+ </gr-tooltip-content>
+ <gr-tooltip-content
has-tooltip=""
position-below="[[showTooltipBelow]]"
title="Unified diff"
- class$="[[_computeUnifiedSelected(mode)]]"
- aria-pressed$="[[isUnifiedSelected(mode)]]"
- on-click="_handleUnifiedTap"
>
- <iron-icon icon="gr-icons:unified"></iron-icon>
- </gr-button>
+ <gr-button
+ id="unifiedBtn"
+ link=""
+ class$="[[_computeUnifiedSelected(mode)]]"
+ aria-pressed$="[[isUnifiedSelected(mode)]]"
+ on-click="_handleUnifiedTap"
+ >
+ <iron-icon icon="gr-icons:unified"></iron-icon>
+ </gr-button>
+ </gr-tooltip-content>
`;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index b1d71cc..bb5ce94 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -16,6 +16,7 @@
*/
import '@polymer/iron-dropdown/iron-dropdown';
import '@polymer/iron-input/iron-input';
+import '../../../styles/gr-a11y-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-dropdown/gr-dropdown';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
index 7e0ca10..4f1047f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
@@ -17,6 +17,9 @@
import {html} from '@polymer/polymer/lib/utils/html-tag';
export const htmlTemplate = html`
+ <style include="gr-a11y-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
:host {
display: block;
@@ -351,15 +354,15 @@
hidden=""
>
<span class="preferences desktop">
- <gr-button
- link=""
- class="prefsButton"
+ <gr-tooltip-content
has-tooltip=""
position-below=""
title="Diff preferences"
- on-click="_handlePrefsTap"
- ><iron-icon icon="gr-icons:settings"></iron-icon
- ></gr-button>
+ >
+ <gr-button link="" class="prefsButton" on-click="_handlePrefsTap"
+ ><iron-icon icon="gr-icons:settings"></iron-icon
+ ></gr-button>
+ </gr-tooltip-content>
</span>
</span>
<gr-endpoint-decorator name="annotation-toggler">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
index fada9cb..7393606 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
@@ -129,6 +129,29 @@
rootId: string;
}
+const VISIBLE_TEXT_NODE_TYPES = [Node.TEXT_NODE, Node.ELEMENT_NODE];
+
+export function getPreviousContentNodes(node?: Node | null) {
+ const sibs = [];
+ while (node) {
+ const {parentNode, previousSibling} = node;
+ const topContentLevel =
+ parentNode &&
+ (parentNode as HTMLElement).classList.contains('contentText');
+ let previousEl: Node | undefined | null;
+ if (previousSibling) {
+ previousEl = previousSibling;
+ } else if (!topContentLevel) {
+ previousEl = parentNode?.previousSibling;
+ }
+ if (previousEl && VISIBLE_TEXT_NODE_TYPES.includes(previousEl.nodeType)) {
+ sibs.push(previousEl);
+ }
+ node = previousEl;
+ }
+ return sibs;
+}
+
export function isThreadEl(node: Node): node is GrDiffThreadElement {
return (
node.nodeType === Node.ELEMENT_NODE &&
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
index 3544834..0d6cadc 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import '../../../styles/gr-a11y-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-dropdown-list/gr-dropdown-list';
import '../../shared/gr-select/gr-select';
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
index 5ab8449..ebbb0d6 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
@@ -17,6 +17,9 @@
import {html} from '@polymer/polymer/lib/utils/html-tag';
export const htmlTemplate = html`
+ <style include="gr-a11y-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
:host {
align-items: center;
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
index bc3c054..3f2258d 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
@@ -45,6 +45,7 @@
override render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const customStyle = html`
<style>
.row {
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
index 91882ab..3adb0f3 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.ts
@@ -19,21 +19,15 @@
import '../../shared/gr-list-view/gr-list-view';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-documentation-search_html';
-import {
- ListViewMixin,
- ListViewParams,
-} from '../../../mixins/gr-list-view-mixin/gr-list-view-mixin';
import {getBaseUrl} from '../../../utils/url-util';
import {customElement, property} from '@polymer/decorators';
import {DocResult} from '../../../types/common';
import {fireTitleChange} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ListViewMixin(PolymerElement);
+import {ListViewParams} from '../../gr-app-types';
@customElement('gr-documentation-search')
-export class GrDocumentationSearch extends base {
+export class GrDocumentationSearch extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -62,7 +56,7 @@
_paramsChanged(params: ListViewParams) {
this._loading = true;
- this._filter = this.getFilterValue(params);
+ this._filter = params?.filter ?? '';
return this._getDocumentationSearches(this._filter);
}
@@ -87,6 +81,10 @@
}
return `${getBaseUrl()}/${url}`;
}
+
+ computeLoadingClass(loading: boolean) {
+ return loading ? 'loading' : '';
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
index 5267b2d..bf6a0d5 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.ts
@@ -21,8 +21,8 @@
import {page} from '../../../utils/page-wrapper-utils';
import 'lodash/lodash';
import {stubRestApi} from '../../../test/test-utils';
-import {ListViewParams} from '../../../mixins/gr-list-view-mixin/gr-list-view-mixin';
import {DocResult} from '../../../types/common';
+import {ListViewParams} from '../../gr-app-types';
const basicFixture = fixtureFromElement('gr-documentation-search');
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
index 1255256..db45c33 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.ts
@@ -59,6 +59,7 @@
override render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const customStyle = html`
<style>
gr-button,
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 46a3e84..b6fe60b 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -353,6 +353,7 @@
this.bindShortcut(Shortcut.REFRESH_CHANGE_LIST, 'shift+r:keyup');
this.bindShortcut(Shortcut.EDIT_TOPIC, 't');
this.bindShortcut(Shortcut.OPEN_SUBMIT_DIALOG, 'shift+s');
+ this.bindShortcut(Shortcut.TOGGLE_ATTENTION_SET, 'shift+t');
this.bindShortcut(Shortcut.OPEN_REPLY_DIALOG, 'a:keyup');
this.bindShortcut(Shortcut.OPEN_DOWNLOAD_DIALOG, 'd:keyup');
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index e5096c0..6c8bdb9 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -52,20 +52,21 @@
groupId: GroupId;
}
-export interface AppElementAdminParams {
+export interface ListViewParams {
+ filter?: string | null;
+ offset?: number | string;
+}
+
+export interface AppElementAdminParams extends ListViewParams {
view: GerritView.ADMIN;
adminView: string;
- offset?: string | number;
- filter?: string | null;
openCreateModal?: boolean;
}
-export interface AppElementRepoParams {
+export interface AppElementRepoParams extends ListViewParams {
view: GerritView.REPO;
detail?: RepoDetailView;
repo: RepoName;
- offset?: string | number;
- filter?: string | null;
}
export interface AppElementDocSearchParams {
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
index 8ed6611..45a93bf 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.ts
@@ -51,6 +51,11 @@
return dom(this.popup) as unknown as HTMLElement;
}
+ appendContent(el: HTMLElement) {
+ if (!this.popup) throw new Error('popup element not (yet) available');
+ this.popup.appendChild(el);
+ }
+
/**
* Opens the popup, inserts it into DOM over current UI.
* Creates the popup if not previously created. Creates popup content element,
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.ts b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.ts
index f913cf6..6580ad6 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-custom-plugin-header.ts
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/* eslint-disable lit/no-legacy-template-syntax,lit/prefer-static-styles */
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {html} from '@polymer/polymer/lib/utils/html-tag';
import {customElement, property} from '@polymer/decorators';
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.ts
index 514f00e..51259c8 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_html.ts
@@ -56,7 +56,7 @@
<span class="title">Registered</span>
<span class="value">
<gr-date-formatter
- has-tooltip=""
+ withTooltip
date-str="[[_account.registered_on]]"
></gr-date-formatter>
</span>
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
index b79e448..96b1ded 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
@@ -15,22 +15,18 @@
* limitations under the License.
*/
import '../../shared/gr-button/gr-button';
-import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../../styles/shared-styles';
import '../../../styles/gr-form-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-table-editor_html';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
import {customElement, property, observe} from '@polymer/decorators';
import {ServerInfo} from '../../../types/common';
import {appContext} from '../../../services/app-context';
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(PolymerElement);
+import {columnNames} from '../../change-list/gr-change-list/gr-change-list';
@customElement('gr-change-table-editor')
-export class GrChangeTableEditor extends base {
+export class GrChangeTableEditor extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -51,18 +47,33 @@
@observe('serverConfig')
_configChanged(config: ServerInfo) {
- this.defaultColumns = this.getEnabledColumns(
- this.columnNames,
- config,
- this.flagsService.enabledExperiments
+ this.defaultColumns = columnNames.filter(col =>
+ this._isColumnEnabled(col, config, this.flagsService.enabledExperiments)
);
if (!this.displayedColumns) return;
this.displayedColumns = this.displayedColumns.filter(column =>
- this.isColumnEnabled(column, config, this.flagsService.enabledExperiments)
+ this._isColumnEnabled(
+ column,
+ config,
+ this.flagsService.enabledExperiments
+ )
);
}
/**
+ * Is the column disabled by a server config or experiment? For example the
+ * assignee feature might be disabled and thus the corresponding column is
+ * also disabled.
+ *
+ */
+ _isColumnEnabled(column: string, config: ServerInfo, experiments: string[]) {
+ if (!config || !config.change) return true;
+ if (column === 'Assignee') return !!config.change.enable_assignee;
+ if (column === 'Comments') return experiments.includes('comments-column');
+ return true;
+ }
+
+ /**
* Get the list of enabled column names from whichever checkboxes are
* checked (excluding the number checkbox).
*/
@@ -79,6 +90,13 @@
.map(checkbox => checkbox.name);
}
+ _computeIsColumnHidden(columnToCheck?: string, columnsToDisplay?: string[]) {
+ if (!columnsToDisplay || !columnToCheck) {
+ return false;
+ }
+ return !columnsToDisplay.includes(columnToCheck);
+ }
+
/**
* Handle a click on a checkbox container and relay the click to the checkbox it
* contains.
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_html.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_html.ts
index a05ec73..e756a20 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_html.ts
@@ -74,7 +74,7 @@
type="checkbox"
name="[[item]]"
on-click="_handleTargetClick"
- checked$="[[!isColumnHidden(item, displayedColumns)]]"
+ checked$="[[!_computeIsColumnHidden(item, displayedColumns)]]"
/>
</td>
</tr>
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
index 4f61972..4f8d0a0 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
@@ -117,7 +117,7 @@
test('_getDisplayedColumns', () => {
const enabledColumns = columns.filter(column =>
- element.isColumnEnabled(column, element.serverConfig!, [])
+ element._isColumnEnabled(column, element.serverConfig!, [])
);
assert.deepEqual(element._getDisplayedColumns(), enabledColumns);
const input = queryAndAssert<HTMLInputElement>(
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
index ace1e1a..4096b02 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
@@ -16,7 +16,6 @@
*/
import '@polymer/iron-input/iron-input';
import '../../shared/gr-button/gr-button';
-import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../../styles/shared-styles';
import '../../../styles/gr-form-styles';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index da4bb0a..94333c7 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -29,7 +29,6 @@
import '../gr-change-table-editor/gr-change-table-editor';
import '../../shared/gr-button/gr-button';
import {GrButton} from '../../shared/gr-button/gr-button';
-import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../shared/gr-diff-preferences/gr-diff-preferences';
import '../../shared/gr-page-nav/gr-page-nav';
import '../../shared/gr-select/gr-select';
@@ -47,7 +46,6 @@
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-settings-view_html';
import {getDocsBaseUrl} from '../../../utils/url-util';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
import {customElement, property, observe} from '@polymer/decorators';
import {AppElementParams} from '../../gr-app-types';
import {GrAccountInfo} from '../gr-account-info/gr-account-info';
@@ -76,6 +74,7 @@
EmailStrategy,
TimeFormat,
} from '../../../constants/constants';
+import {columnNames} from '../../change-list/gr-change-list/gr-change-list';
const PREFS_SECTION_FIELDS: Array<keyof PreferencesInput> = [
'changes_per_page',
@@ -139,11 +138,8 @@
};
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(PolymerElement);
-
@customElement('gr-settings-view')
-export class GrSettingsView extends base {
+export class GrSettingsView extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -262,8 +258,10 @@
this._localMenu = this._cloneMenu(prefs.my);
this._localChangeTableColumns =
prefs.change_table.length === 0
- ? this.columnNames
- : this.renameProjectToRepoColumn(prefs.change_table);
+ ? columnNames
+ : prefs.change_table.map(column =>
+ column === 'Project' ? 'Repo' : column
+ );
})
);
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
index 66103fd..31c62b1 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
@@ -128,6 +128,7 @@
override render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const customStyle = html`
<style>
.container {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
index f746c29..9897a9f 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -206,18 +206,7 @@
></gr-hovercard-account>`
: ''}
${hasAttention
- ? html`<gr-button
- id="attentionButton"
- link=""
- aria-label="Remove user from attention set"
- @click=${this._handleRemoveAttentionClick}
- ?disabled=${!this._computeAttentionButtonEnabled(
- highlightAttention,
- account,
- change,
- this.selected,
- this._selfAccount
- )}
+ ? html` <gr-tooltip-content
?has-tooltip=${this._computeAttentionButtonEnabled(
highlightAttention,
account,
@@ -233,11 +222,25 @@
this.selected,
this._selfAccount
)}"
- ><iron-icon
- class="attention"
- icon="gr-icons:attention"
- ></iron-icon>
- </gr-button>`
+ >
+ <gr-button
+ id="attentionButton"
+ link=""
+ aria-label="Remove user from attention set"
+ @click=${this._handleRemoveAttentionClick}
+ ?disabled=${!this._computeAttentionButtonEnabled(
+ highlightAttention,
+ account,
+ change,
+ this.selected,
+ this._selfAccount
+ )}
+ ><iron-icon
+ class="attention"
+ icon="gr-icons:attention"
+ ></iron-icon>
+ </gr-button>
+ </gr-tooltip-content>`
: ''}
</span>
<span
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
index b302bee..fbf29c63 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
@@ -95,7 +95,7 @@
?hideStatus=${this.hideStatus}
?firstName=${this.firstName}
.voteableText=${this.voteableText}
- part="gr-account-link-text => gr-account-label-text"
+ exportparts="gr-account-label-text: gr-account-link-text"
>
</gr-account-label>
</a>
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
index c38c196..ae3853e 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.ts
@@ -96,6 +96,7 @@
override render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const style = html`<style>
.action {
--gr-button: {
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
index 1ece10a..f7ebd66 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -15,14 +15,11 @@
* limitations under the License.
*/
import '@polymer/paper-button/paper-button';
-import '../../../styles/shared-styles';
-import '../../../styles/gr-voting-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {customElement, property, computed, observe} from '@polymer/decorators';
-import {htmlTemplate} from './gr-button_html';
-import {TooltipMixin} from '../../../mixins/gr-tooltip-mixin/gr-tooltip-mixin';
+import {spinnerStyles} from '../../../styles/gr-spinner-styles';
+import {votingStyles} from '../../../styles/gr-voting-styles';
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {customElement, property} from 'lit/decorators';
import {
- PolymerEvent,
getEventPath,
getKeyboardEvent,
isModifierPressed,
@@ -37,87 +34,243 @@
}
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = TooltipMixin(PolymerElement);
-
@customElement('gr-button')
-export class GrButton extends base {
- static get template() {
- return htmlTemplate;
- }
+export class GrButton extends LitElement {
+ private readonly reporting: ReportingService = appContext.reportingService;
/**
* Should this button be rendered as a vote chip? Then we are applying
* the .voteChip class (see gr-voting-styles) to the paper-button.
*/
- @property({type: Boolean, reflectToAttribute: true})
+ @property({type: Boolean, reflect: true})
voteChip = false;
- @property({type: Boolean, reflectToAttribute: true})
- downArrow = false;
-
- @property({type: Boolean, reflectToAttribute: true})
- link = false;
-
- @property({type: Boolean})
- noUppercase = false;
-
- @property({type: Boolean, reflectToAttribute: true})
- loading = false;
-
- @property({type: Boolean, reflectToAttribute: true})
- disabled: boolean | null = null;
-
- @property({type: String})
- tooltip = '';
-
// Note: don't assign a value to this, since constructor is called
// after created, the initial value maybe overridden by this
- @property({type: String})
- _initialTabindex?: string;
+ private initialTabindex?: string;
- @computed('disabled', 'loading')
- get _disabled() {
- return this.disabled || this.loading;
+ @property({type: Boolean, reflect: true})
+ downArrow = false;
+
+ @property({type: Boolean, reflect: true})
+ link = false;
+
+ @property({type: Boolean, reflect: true})
+ loading = false;
+
+ @property({type: Boolean, reflect: true})
+ disabled: boolean | null = null;
+
+ static override get styles() {
+ return [
+ votingStyles,
+ spinnerStyles,
+ css`
+ /* general styles for all buttons */
+ :host {
+ --background-color: var(
+ --button-background-color,
+ var(--default-button-background-color)
+ );
+ --text-color: var(--default-button-text-color);
+ display: inline-block;
+ position: relative;
+ }
+ :host([hidden]) {
+ display: none;
+ }
+ :host([no-uppercase]) paper-button {
+ text-transform: none;
+ }
+ paper-button {
+ /* The next lines contains a copy of paper-button style.
+ Without a copy, the @apply works incorrectly with Polymer 2.
+ @apply is deprecated and is not recommended to use. It is expected
+ that @apply will be replaced with the ::part CSS pseudo-element.
+ After replacement copied lines can be removed.
+ */
+ @apply --layout-inline;
+ @apply --layout-center-center;
+ position: relative;
+ box-sizing: border-box;
+ min-width: 5.14em;
+ margin: 0 0.29em;
+ background: transparent;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ -webkit-tap-highlight-color: transparent;
+ font: inherit;
+ text-transform: uppercase;
+ outline-width: 0;
+ border-top-left-radius: var(--border-radius);
+ border-top-right-radius: var(--border-radius);
+ border-bottom-right-radius: var(--border-radius);
+ border-bottom-left-radius: var(--border-radius);
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+ cursor: pointer;
+ z-index: 0;
+ padding: var(--spacing-m);
+
+ @apply --paper-font-common-base;
+ @apply --paper-button;
+ /* End of copy*/
+
+ /* paper-button sets this to anti-aliased, which appears different than
+ bold font elsewhere on macOS. */
+ -webkit-font-smoothing: initial;
+ align-items: center;
+ background-color: var(--background-color);
+ color: var(--text-color);
+ display: flex;
+ font-family: inherit;
+ justify-content: center;
+ margin: var(--margin, 0);
+ min-width: var(--border, 0);
+ padding: var(--padding, 4px 8px);
+ @apply --gr-button;
+ }
+ /* https://github.com/PolymerElements/paper-button/blob/2.x/paper-button.html */
+ /* BEGIN: Copy from paper-button */
+ paper-button[elevation='1'] {
+ @apply --paper-material-elevation-1;
+ }
+ paper-button[elevation='2'] {
+ @apply --paper-material-elevation-2;
+ }
+ paper-button[elevation='3'] {
+ @apply --paper-material-elevation-3;
+ }
+ paper-button[elevation='4'] {
+ @apply --paper-material-elevation-4;
+ }
+ paper-button[elevation='5'] {
+ @apply --paper-material-elevation-5;
+ }
+ /* END: Copy from paper-button */
+ paper-button:hover {
+ background: linear-gradient(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.12)),
+ var(--background-color);
+ }
+
+ /* Some mobile browsers treat focused element as hovered element.
+ As a result, element remains hovered after click (has grey background in default theme).
+ Use @media (hover:none) to remove background if
+ user's primary input mechanism can't hover over elements.
+ See: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover
+
+ Note 1: not all browsers support this media query
+ (see https://caniuse.com/#feat=css-media-interaction).
+ If browser doesn't support it, then the whole content of @media .. is ignored.
+ This is why the default behavior is placed outside of @media.
+ */
+ @media (hover: none) {
+ paper-button:hover {
+ background: transparent;
+ }
+ }
+
+ :host([primary]) {
+ --background-color: var(--primary-button-background-color);
+ --text-color: var(--primary-button-text-color);
+ }
+ :host([link][primary]) {
+ --text-color: var(--primary-button-background-color);
+ }
+
+ /* Keep below color definition for primary so that this takes precedence
+ when disabled. */
+ :host([disabled]),
+ :host([loading]) {
+ --background-color: var(--disabled-button-background-color);
+ --text-color: var(--deemphasized-text-color);
+ cursor: default;
+ }
+
+ /* Styles for link buttons specifically */
+ :host([link]) {
+ --background-color: transparent;
+ --margin: 0;
+ --padding: var(--spacing-s);
+ }
+ :host([disabled][link]),
+ :host([loading][link]) {
+ --background-color: transparent;
+ --text-color: var(--deemphasized-text-color);
+ cursor: default;
+ }
+
+ /* Styles for the optional down arrow */
+ :host(:not([down-arrow])) .downArrow {
+ display: none;
+ }
+ :host([down-arrow]) .downArrow {
+ border-top: 0.36em solid #ccc;
+ border-left: 0.36em solid transparent;
+ border-right: 0.36em solid transparent;
+ margin-bottom: var(--spacing-xxs);
+ margin-left: var(--spacing-m);
+ transition: border-top-color 200ms;
+ }
+ :host([down-arrow]) paper-button:hover .downArrow {
+ border-top-color: var(--deemphasized-text-color);
+ }
+ `,
+ ];
}
- @property({
- computed: 'computeAriaDisabled(disabled, loading)',
- reflectToAttribute: true,
- type: String,
- })
- ariaDisabled!: string;
-
- computeAriaDisabled() {
- return this._disabled ? 'true' : 'false';
+ override render() {
+ return html`<paper-button
+ ?raised="${!this.link}"
+ ?disabled="${this.disabled || this.loading}"
+ role="button"
+ tabindex="-1"
+ part="paper-button"
+ class="${this.voteChip ? 'voteChip' : ''}"
+ >
+ ${this.loading ? html`<span class="loadingSpin"></span>` : ''}
+ <slot></slot>
+ <i class="downArrow"></i>
+ </paper-button>`;
}
- computePaperButtonClass(voteChip?: boolean) {
- return voteChip ? 'voteChip' : '';
- }
-
- private readonly reporting: ReportingService = appContext.reportingService;
-
constructor() {
super();
- this._initialTabindex = this.getAttribute('tabindex') || '0';
- // TODO(TS): try avoid using unknown
- this.addEventListener('click', e =>
- this._handleAction(e as unknown as PolymerEvent)
- );
+ this.initialTabindex = this.getAttribute('tabindex') || '0';
+ this.addEventListener('click', e => this._handleAction(e));
this.addEventListener('keydown', e =>
this._handleKeydown(e as unknown as CustomKeyboardEvent)
);
}
- override ready() {
- super.ready();
- this._ensureAttribute('role', 'button');
- this._ensureAttribute('tabindex', '0');
+ override updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('disabled')) {
+ this.setAttribute(
+ 'tabindex',
+ this.disabled ? '-1' : this.initialTabindex || '0'
+ );
+ }
+ if (changedProperties.has('loading') || changedProperties.has('disabled')) {
+ this.setAttribute(
+ 'aria-disabled',
+ this.disabled || this.loading ? 'true' : 'false'
+ );
+ }
}
- _handleAction(e: PolymerEvent) {
- if (this._disabled) {
+ override connectedCallback() {
+ super.connectedCallback();
+ if (!this.getAttribute('role')) {
+ this.setAttribute('role', 'button');
+ }
+ if (!this.getAttribute('tabindex')) {
+ this.setAttribute('tabindex', '0');
+ }
+ }
+
+ _handleAction(e: MouseEvent) {
+ if (this.disabled || this.loading) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
@@ -127,15 +280,6 @@
this.reporting.reportInteraction('button-click', {path: getEventPath(e)});
}
- @observe('disabled')
- _disabledChanged(disabled: boolean) {
- this.setAttribute(
- 'tabindex',
- disabled ? '-1' : this._initialTabindex || '0'
- );
- this.updateStyles();
- }
-
_handleKeydown(e: CustomKeyboardEvent) {
if (isModifierPressed(e)) {
return;
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_html.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button_html.ts
deleted file mode 100644
index 22ec2f4..0000000
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_html.ts
+++ /dev/null
@@ -1,188 +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-voting-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="gr-spinner-styles">
- /* general styles for all buttons */
- :host {
- --background-color: var(
- --button-background-color,
- var(--default-button-background-color)
- );
- --text-color: var(--default-button-text-color);
- display: inline-block;
- position: relative;
- }
- :host([hidden]) {
- display: none;
- }
- :host([no-uppercase]) paper-button {
- text-transform: none;
- }
- paper-button {
- /* The next lines contains a copy of paper-button style.
- Without a copy, the @apply works incorrectly with Polymer 2.
- @apply is deprecated and is not recommended to use. It is expected
- that @apply will be replaced with the ::part CSS pseudo-element.
- After replacement copied lines can be removed.
- */
- @apply --layout-inline;
- @apply --layout-center-center;
- position: relative;
- box-sizing: border-box;
- min-width: 5.14em;
- margin: 0 0.29em;
- background: transparent;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
- -webkit-tap-highlight-color: transparent;
- font: inherit;
- text-transform: uppercase;
- outline-width: 0;
- border-top-left-radius: var(--border-radius);
- border-top-right-radius: var(--border-radius);
- border-bottom-right-radius: var(--border-radius);
- border-bottom-left-radius: var(--border-radius);
- -moz-user-select: none;
- -ms-user-select: none;
- -webkit-user-select: none;
- user-select: none;
- cursor: pointer;
- z-index: 0;
- padding: var(--spacing-m);
-
- @apply --paper-font-common-base;
- @apply --paper-button;
- /* End of copy*/
-
- /* paper-button sets this to anti-aliased, which appears different than
- bold font elsewhere on macOS. */
- -webkit-font-smoothing: initial;
- align-items: center;
- background-color: var(--background-color);
- color: var(--text-color);
- display: flex;
- font-family: inherit;
- justify-content: center;
- margin: var(--margin, 0);
- min-width: var(--border, 0);
- padding: var(--padding, 4px 8px);
- @apply --gr-button;
- }
- /* https://github.com/PolymerElements/paper-button/blob/2.x/paper-button.html */
- /* BEGIN: Copy from paper-button */
- paper-button[elevation='1'] {
- @apply --paper-material-elevation-1;
- }
- paper-button[elevation='2'] {
- @apply --paper-material-elevation-2;
- }
- paper-button[elevation='3'] {
- @apply --paper-material-elevation-3;
- }
- paper-button[elevation='4'] {
- @apply --paper-material-elevation-4;
- }
- paper-button[elevation='5'] {
- @apply --paper-material-elevation-5;
- }
- /* END: Copy from paper-button */
- paper-button:hover {
- background: linear-gradient(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.12)),
- var(--background-color);
- }
-
- /* Some mobile browsers treat focused element as hovered element.
- As a result, element remains hovered after click (has grey background in default theme).
- Use @media (hover:none) to remove background if
- user's primary input mechanism can't hover over elements.
- See: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover
-
- Note 1: not all browsers support this media query
- (see https://caniuse.com/#feat=css-media-interaction).
- If browser doesn't support it, then the whole content of @media .. is ignored.
- This is why the default behavior is placed outside of @media.
- */
- @media (hover: none) {
- paper-button:hover {
- background: transparent;
- }
- }
-
- :host([primary]) {
- --background-color: var(--primary-button-background-color);
- --text-color: var(--primary-button-text-color);
- }
- :host([link][primary]) {
- --text-color: var(--primary-button-background-color);
- }
-
- /* Keep below color definition for primary so that this takes precedence
- when disabled. */
- :host([disabled]),
- :host([loading]) {
- --background-color: var(--disabled-button-background-color);
- --text-color: var(--deemphasized-text-color);
- cursor: default;
- }
-
- /* Styles for link buttons specifically */
- :host([link]) {
- --background-color: transparent;
- --margin: 0;
- --padding: var(--spacing-s);
- }
- :host([disabled][link]),
- :host([loading][link]) {
- --background-color: transparent;
- --text-color: var(--deemphasized-text-color);
- cursor: default;
- }
-
- /* Styles for the optional down arrow */
- :host(:not([down-arrow])) .downArrow {
- display: none;
- }
- :host([down-arrow]) .downArrow {
- border-top: 0.36em solid #ccc;
- border-left: 0.36em solid transparent;
- border-right: 0.36em solid transparent;
- margin-bottom: var(--spacing-xxs);
- margin-left: var(--spacing-m);
- transition: border-top-color 200ms;
- }
- :host([down-arrow]) paper-button:hover .downArrow {
- border-top-color: var(--deemphasized-text-color);
- }
- </style>
- <paper-button
- raised="[[!link]]"
- disabled="[[_disabled]]"
- tabindex="-1"
- part="paper-button"
- class$="[[computePaperButtonClass(voteChip)]]"
- >
- <template is="dom-if" if="[[loading]]">
- <span class="loadingSpin"></span>
- </template>
- <slot></slot>
- <i class="downArrow"></i>
- </paper-button>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
index f0f122a..0149bd5 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.ts
@@ -17,6 +17,7 @@
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import '../../../test/common-test-setup-karma';
+import './gr-button';
import {addListener} from '@polymer/polymer/lib/utils/gestures';
import {appContext} from '../../../services/app-context';
import {html} from '@polymer/polymer/lib/utils/html-tag';
@@ -49,23 +50,26 @@
return spy;
};
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await element.updateComplete;
});
- test('disabled is set by disabled', () => {
+ test('disabled is set by disabled', async () => {
const paperBtn = queryAndAssert<PaperButtonElement>(
element,
'paper-button'
);
assert.isFalse(paperBtn.disabled);
element.disabled = true;
+ await element.updateComplete;
assert.isTrue(paperBtn.disabled);
element.disabled = false;
+ await element.updateComplete;
assert.isFalse(paperBtn.disabled);
});
- test('loading set from listener', () => {
+ test('loading set from listener', async () => {
let resolve: Function;
element.addEventListener('click', e => {
const target = e.target as HTMLElement;
@@ -78,36 +82,44 @@
);
assert.isFalse(paperBtn.disabled);
MockInteractions.tap(element);
+ await element.updateComplete;
assert.isTrue(paperBtn.disabled);
assert.isTrue(element.hasAttribute('loading'));
resolve!();
- flush();
+ await element.updateComplete;
assert.isFalse(paperBtn.disabled);
assert.isFalse(element.hasAttribute('loading'));
});
- test('tabindex should be -1 if disabled', () => {
+ test('tabindex should be -1 if disabled', async () => {
element.disabled = true;
- assert.isTrue(element.getAttribute('tabindex') === '-1');
+ await element.updateComplete;
+ assert.equal(element.getAttribute('tabindex'), '-1');
});
// Regression tests for Issue: 11969
- test('tabindex should be reset to 0 if enabled', () => {
+ test('tabindex should be reset to 0 if enabled', async () => {
element.disabled = false;
+ await element.updateComplete;
assert.equal(element.getAttribute('tabindex'), '0');
element.disabled = true;
+ await element.updateComplete;
assert.equal(element.getAttribute('tabindex'), '-1');
element.disabled = false;
+ await element.updateComplete;
assert.equal(element.getAttribute('tabindex'), '0');
});
- test('tabindex should be preserved', () => {
+ test('tabindex should be preserved', async () => {
const tabIndexElement = tabindexFixture.instantiate() as GrButton;
tabIndexElement.disabled = false;
+ await element.updateComplete;
assert.equal(tabIndexElement.getAttribute('tabindex'), '3');
tabIndexElement.disabled = true;
+ await element.updateComplete;
assert.equal(tabIndexElement.getAttribute('tabindex'), '-1');
tabIndexElement.disabled = false;
+ await element.updateComplete;
assert.equal(tabIndexElement.getAttribute('tabindex'), '3');
});
@@ -152,8 +164,9 @@
}
suite('disabled', () => {
- setup(() => {
+ setup(async () => {
element.disabled = true;
+ await element.updateComplete;
});
for (const eventName of ['tap', 'click']) {
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
index 2ca2744b..455bd4e 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_html.ts
@@ -80,8 +80,8 @@
}
</style>
<gr-tooltip-content
- has-tooltip=""
- position-below=""
+ has-tooltip
+ position-below
title="[[tooltipText]]"
max-width="40em"
>
@@ -101,9 +101,8 @@
</a>
</template>
<template is="dom-if" if="[[!hasStatusLink(revertedChange, resolveWeblinks, status)]]">
- <div class="chip" aria-label$="Label: [[status]]">
- [[_computeStatusString(status)]]
- </div>
+ <div class="chip" aria-label$="Label: [[status]]"
+ >[[_computeStatusString(status)]]</div>
</template>
</gr-tooltip-content>
</span>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 82f6b46..b37613a 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import '../../../styles/gr-a11y-styles';
import '../../../styles/shared-styles';
import '../gr-comment/gr-comment';
import '../../diff/gr-diff/gr-diff';
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
index bf9a9dc..0752a82 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
@@ -17,6 +17,9 @@
import {html} from '@polymer/polymer/lib/utils/html-tag';
export const htmlTemplate = html`
+ <style include="gr-a11y-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
<style include="shared-styles">
:host {
font-family: var(--font-family);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 418cd0e..fa04860 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -20,7 +20,6 @@
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
import '../gr-button/gr-button';
import '../gr-dialog/gr-dialog';
-import '../gr-date-formatter/gr-date-formatter';
import '../gr-formatted-text/gr-formatted-text';
import '../gr-icons/gr-icons';
import '../gr-overlay/gr-overlay';
@@ -675,7 +674,13 @@
@observe('comment.message')
_commentMessageChanged(message: string) {
- this._messageText = message || '';
+ /*
+ * Only overwrite the message text user has typed if there is no existing
+ * text typed by the user. This prevents the bug where creating another
+ * comment triggered a recomputation of comments and the text written by
+ * the user was lost.
+ */
+ if (!this._messageText) this._messageText = message || '';
}
_messageTextChanged(_: string, oldValue: string) {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
index b00bf8b..d1496fb 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_html.ts
@@ -275,10 +275,10 @@
</template>
<gr-tooltip-content
class="draftTooltip"
- has-tooltip=""
+ has-tooltip
title="[[_computeDraftTooltip(_unableToSave)]]"
max-width="20em"
- show-icon=""
+ show-icon
>
<span class="draftLabel">[[_computeDraftText(_unableToSave)]]</span>
</gr-tooltip-content>
@@ -313,7 +313,7 @@
<template is="dom-if" if="[[comment.updated]]">
<span class="date" tabindex="0" on-click="_handleAnchorClick">
<gr-date-formatter
- has-tooltip=""
+ withTooltip
date-str="[[comment.updated]]"
></gr-date-formatter>
</span>
@@ -357,7 +357,7 @@
<div class="respectfulReviewTip">
<div>
<gr-tooltip-content
- has-tooltip=""
+ has-tooltip
title="Tips for respectful code reviews."
>
<iron-icon
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index 31a1614..b963d4b 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -217,6 +217,29 @@
assert.isTrue(storageStub.called);
});
+ test('comment message sets messageText only when empty', () => {
+ element.changeNum = 1 as NumericChangeId;
+ element.patchNum = 1 as PatchSetNum;
+ element._messageText = '';
+ element.comment = {
+ author: {
+ name: 'Mr. Peanutbutter',
+ email: 'tenn1sballchaser@aol.com' as EmailAddress,
+ },
+ line: 5,
+ path: 'test',
+ __editing: true,
+ __draft: true,
+ message: 'hello world',
+ };
+ // messageText was empty so overwrite the message now
+ assert.equal(element._messageText, 'hello world');
+
+ element.comment!.message = 'new message';
+ // messageText was already set so do not overwrite it
+ assert.equal(element._messageText, 'hello world');
+ });
+
test('_getPatchNum', () => {
element.side = 'PARENT';
element.patchNum = 1 as PatchSetNum;
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
index 2e95bd0..0cd522a 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.ts
@@ -89,6 +89,7 @@
override render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const customStyle = html`
<style>
iron-icon {
@@ -109,7 +110,7 @@
type="text"
@click="${this._handleInputClick}"
readonly=""
- bind-value=${this.text}
+ bind-value=${this.text || ''}
>
<input
id="input"
@@ -118,21 +119,24 @@
type="text"
@click="${this._handleInputClick}"
readonly=""
- .value=${this.text}
+ .value=${this.text || ''}
part="text-container-style"
/>
</iron-input>
- <gr-button
- id="copy-clipboard-button"
- link=""
+ <gr-tooltip-content
?has-tooltip=${this.hasTooltip}
- class="copyToClipboard"
title="${ifDefined(this.buttonTitle)}"
- @click="${this._copyToClipboard}"
- aria-label="Click to copy to clipboard"
>
- <iron-icon id="icon" icon="gr-icons:content-copy"></iron-icon>
- </gr-button>
+ <gr-button
+ id="copy-clipboard-button"
+ link=""
+ class="copyToClipboard"
+ @click="${this._copyToClipboard}"
+ aria-label="Click to copy to clipboard"
+ >
+ <iron-icon id="icon" icon="gr-icons:content-copy"></iron-icon>
+ </gr-button>
+ </gr-tooltip-content>
</div> `;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
index a3800a8..99f9265 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.ts
@@ -14,11 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-date-formatter_html';
-import {TooltipMixin} from '../../../mixins/gr-tooltip-mixin/gr-tooltip-mixin';
-import {property, customElement} from '@polymer/decorators';
+import '../gr-tooltip-content/gr-tooltip-content';
+import {css, html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators';
import {
parseDate,
fromNow,
@@ -75,17 +73,10 @@
}
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = TooltipMixin(PolymerElement);
-
@customElement('gr-date-formatter')
-export class GrDateFormatter extends base {
- static get template() {
- return htmlTemplate;
- }
-
- @property({type: String, notify: true})
- dateStr: string | null = null;
+export class GrDateFormatter extends LitElement {
+ @property({type: String})
+ dateStr: string | undefined = undefined;
@property({type: Boolean})
showDateAndTime = false;
@@ -95,30 +86,20 @@
* native browser tooltip.
*/
@property({type: Boolean})
- override hasTooltip = false;
+ withTooltip = false;
@property({type: Boolean})
showYesterday = false;
- /**
- * The title to be used as the native tooltip or by the tooltip behavior.
- */
- @property({
- type: String,
- reflectToAttribute: true,
- computed: '_computeFullDateStr(dateStr, _timeFormat, _dateFormat)',
- })
- override title = '';
-
/** @type {?{short: string, full: string}} */
@property({type: Object})
- _dateFormat?: DateFormatPair;
+ private dateFormat?: DateFormatPair;
@property({type: String})
- _timeFormat?: string;
+ private timeFormat?: string;
@property({type: Boolean})
- _relative = false;
+ private relative = false;
@property({type: Boolean})
forceRelative = false;
@@ -132,76 +113,110 @@
super();
}
+ static override get styles() {
+ return [
+ css`
+ host {
+ color: inherit;
+ display: inline;
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ if (!this.withTooltip) {
+ return this.renderDateString();
+ }
+
+ const fullDateStr = this.computeFullDateStr();
+ if (!fullDateStr) {
+ return this.renderDateString();
+ }
+ return html`
+ <gr-tooltip-content has-tooltip title=${fullDateStr}>
+ ${this.renderDateString()}
+ </gr-tooltip-content>
+ `;
+ }
+
+ private renderDateString() {
+ return html` <span>${this._computeDateStr()}</span>`;
+ }
+
override connectedCallback() {
super.connectedCallback();
this._loadPreferences();
}
+ // private but used by tests
_getUtcOffsetString() {
return utcOffsetString();
}
+ // private but used by tests
_loadPreferences() {
return this._getLoggedIn().then(loggedIn => {
if (!loggedIn) {
- this._timeFormat = TimeFormats.TIME_24;
- this._dateFormat = DateFormats.STD;
- this._relative = this.forceRelative;
+ this.timeFormat = TimeFormats.TIME_24;
+ this.dateFormat = DateFormats.STD;
+ this.relative = this.forceRelative;
return;
}
- return Promise.all([this._loadTimeFormat(), this._loadRelative()]);
+ return Promise.all([this._loadTimeFormat(), this.loadRelative()]);
});
}
+ // private but used in gr/file-list_test.js
_loadTimeFormat() {
- return this._getPreferences().then(preferences => {
+ return this.getPreferences().then(preferences => {
if (!preferences) {
throw Error('Preferences is not set');
}
- this._decideTimeFormat(preferences.time_format);
- this._decideDateFormat(preferences.date_format);
+ this.decideTimeFormat(preferences.time_format);
+ this.decideDateFormat(preferences.date_format);
});
}
- _decideTimeFormat(timeFormat: TimeFormat) {
+ private decideTimeFormat(timeFormat: TimeFormat) {
switch (timeFormat) {
case TimeFormat.HHMM_12:
- this._timeFormat = TimeFormats.TIME_12;
+ this.timeFormat = TimeFormats.TIME_12;
break;
case TimeFormat.HHMM_24:
- this._timeFormat = TimeFormats.TIME_24;
+ this.timeFormat = TimeFormats.TIME_24;
break;
default:
assertNever(timeFormat, `Invalid time format: ${timeFormat}`);
}
}
- _decideDateFormat(dateFormat: DateFormat) {
+ private decideDateFormat(dateFormat: DateFormat) {
switch (dateFormat) {
case DateFormat.STD:
- this._dateFormat = DateFormats.STD;
+ this.dateFormat = DateFormats.STD;
break;
case DateFormat.US:
- this._dateFormat = DateFormats.US;
+ this.dateFormat = DateFormats.US;
break;
case DateFormat.ISO:
- this._dateFormat = DateFormats.ISO;
+ this.dateFormat = DateFormats.ISO;
break;
case DateFormat.EURO:
- this._dateFormat = DateFormats.EURO;
+ this.dateFormat = DateFormats.EURO;
break;
case DateFormat.UK:
- this._dateFormat = DateFormats.UK;
+ this.dateFormat = DateFormats.UK;
break;
default:
assertNever(dateFormat, `Invalid date format: ${dateFormat}`);
}
}
- _loadRelative() {
- return this._getPreferences().then(prefs => {
+ private loadRelative() {
+ return this.getPreferences().then(prefs => {
// prefs.relative_date_in_change_table is not set when false.
- this._relative =
+ this.relative =
this.forceRelative || !!(prefs && prefs.relative_date_in_change_table);
});
}
@@ -210,70 +225,60 @@
return this.restApiService.getLoggedIn();
}
- _getPreferences() {
+ private getPreferences() {
return this.restApiService.getPreferences();
}
- _computeDateStr(
- dateStr?: Timestamp,
- timeFormat?: string,
- dateFormat?: DateFormatPair,
- relative?: boolean,
- showDateAndTime?: boolean,
- showYesterday?: boolean
- ) {
- if (!dateStr || !timeFormat || !dateFormat) {
+ // private but used by tests
+ _computeDateStr() {
+ if (!this.dateStr || !this.timeFormat || !this.dateFormat) {
return '';
}
- const date = parseDate(dateStr);
+ const date = parseDate(this.dateStr as Timestamp);
if (!isValidDate(date)) {
return '';
}
- if (relative) {
+ if (this.relative) {
return fromNow(date, this.relativeOptionNoAgo);
}
const now = new Date();
- let format = dateFormat.full;
+ let format = this.dateFormat.full;
if (isWithinDay(now, date)) {
- format = timeFormat;
- } else if (showYesterday && wasYesterday(now, date)) {
- return `Yesterday at ${formatDate(date, timeFormat)}`;
+ format = this.timeFormat;
+ } else if (this.showYesterday && wasYesterday(now, date)) {
+ return `Yesterday at ${formatDate(date, this.timeFormat)}`;
} else {
if (isWithinHalfYear(now, date)) {
- format = dateFormat.short;
+ format = this.dateFormat.short;
}
- if (this.showDateAndTime || showDateAndTime) {
- format = `${format} ${timeFormat}`;
+ if (this.showDateAndTime || this.showDateAndTime) {
+ format = `${format} ${this.timeFormat}`;
}
}
return formatDate(date, format);
}
- _timeToSecondsFormat(timeFormat: string | undefined) {
- return timeFormat === TimeFormats.TIME_12
- ? TimeFormats.TIME_12_WITH_SEC
- : TimeFormats.TIME_24_WITH_SEC;
- }
-
- _computeFullDateStr(
- dateStr?: Timestamp,
- timeFormat?: string,
- dateFormat?: DateFormatPair
- ) {
+ private computeFullDateStr() {
// Polymer 2: check for undefined
- if ([dateStr, timeFormat].includes(undefined) || !dateFormat) {
+ if (
+ [this.dateStr, this.timeFormat].includes(undefined) ||
+ !this.dateFormat
+ ) {
return undefined;
}
- if (!dateStr) {
+ if (!this.dateStr) {
return '';
}
- const date = parseDate(dateStr);
+ const date = parseDate(this.dateStr as Timestamp);
if (!isValidDate(date)) {
return '';
}
- let format = dateFormat.full + ', ';
- format += this._timeToSecondsFormat(timeFormat);
+ let format = this.dateFormat.full + ', ';
+ format +=
+ this.timeFormat === TimeFormats.TIME_12
+ ? TimeFormats.TIME_12_WITH_SEC
+ : TimeFormats.TIME_24_WITH_SEC;
return formatDate(date, format) + this._getUtcOffsetString();
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_html.ts b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_html.ts
deleted file mode 100644
index 4808832..0000000
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_html.ts
+++ /dev/null
@@ -1,30 +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>
- :host {
- color: inherit;
- display: inline;
- }
- </style>
- <span>
- [[_computeDateStr(dateStr, _timeFormat, _dateFormat, _relative,
- showDateAndTime, showYesterday)]]
- </span>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.js b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.js
index 9a96c2d..860a7e7 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.js
@@ -22,14 +22,18 @@
import {stubRestApi} from '../../../test/test-utils.js';
const basicFixture = fixtureFromTemplate(html`
-<gr-date-formatter date-str="2015-09-24 23:30:17.033000000"></gr-date-formatter>
+<gr-date-formatter withTooltip dateStr="2015-09-24 23:30:17.033000000">
+</gr-date-formatter>
+`);
+
+const lightFixture = fixtureFromTemplate(html`
+<gr-date-formatter dateStr="2015-09-24 23:30:17.033000000"></gr-date-formatter>
`);
suite('gr-date-formatter tests', () => {
let element;
setup(() => {
-
});
/**
@@ -41,7 +45,7 @@
return d;
}
- function testDates(nowStr, dateStr, expected, expectedWithDateAndTime,
+ async function testDates(nowStr, dateStr, expected, expectedWithDateAndTime,
expectedTooltip) {
// Normalize and convert the date to mimic server response.
dateStr = normalizedDate(dateStr)
@@ -50,13 +54,13 @@
.slice(0, -1);
sinon.useFakeTimers(normalizedDate(nowStr).getTime());
element.dateStr = dateStr;
- flush();
- const span = element.shadowRoot
- .querySelector('span');
+ await element.updateComplete;
+ const span = element.shadowRoot.querySelector('span');
+ const tooltip = element.shadowRoot.querySelector('gr-tooltip-content');
assert.equal(span.textContent.trim(), expected);
- assert.equal(element.title, expectedTooltip);
+ assert.equal(tooltip.title, expectedTooltip);
element.showDateAndTime = true;
- flush();
+ await element.updateComplete;
assert.equal(span.textContent.trim(), expectedWithDateAndTime);
}
@@ -81,35 +85,37 @@
test('invalid dates are quietly rejected', () => {
assert.notOk((new Date('foo')).valueOf());
- assert.equal(element._computeDateStr('foo', 'h:mm A'), '');
+ element.dateStr = 'foo';
+ element.timeFormat = 'h:mm A';
+ assert.equal(element._computeDateStr(), '');
});
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'15:34',
'15:34',
'Jul 29, 2015, 15:34:14');
});
- test('Within 24 hours on different days', () => {
- testDates('2015-07-29 03:34:14.985000000',
+ test('Within 24 hours on different days', async () => {
+ await testDates('2015-07-29 03:34:14.985000000',
'2015-07-28 20:25:14.985000000',
'Jul 28',
'Jul 28 20:25',
'Jul 28, 2015, 20:25:14');
});
- test('More than 24 hours but less than six months', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('More than 24 hours but less than six months', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-06-15 03:25:14.985000000',
'Jun 15',
'Jun 15 03:25',
'Jun 15, 2015, 03:25:14');
});
- test('More than six months', () => {
- testDates('2015-09-15 20:34:00.000000000',
+ test('More than six months', async () => {
+ await testDates('2015-09-15 20:34:00.000000000',
'2015-01-15 03:25:00.000000000',
'Jan 15, 2015',
'Jan 15, 2015 03:25',
@@ -128,24 +134,24 @@
return element._loadPreferences();
}));
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'15:34',
'15:34',
'07/29/15, 15:34:14');
});
- test('Within 24 hours on different days', () => {
- testDates('2015-07-29 03:34:14.985000000',
+ test('Within 24 hours on different days', async () => {
+ await testDates('2015-07-29 03:34:14.985000000',
'2015-07-28 20:25:14.985000000',
'07/28',
'07/28 20:25',
'07/28/15, 20:25:14');
});
- test('More than 24 hours but less than six months', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('More than 24 hours but less than six months', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-06-15 03:25:14.985000000',
'06/15',
'06/15 03:25',
@@ -164,24 +170,24 @@
return element._loadPreferences();
}));
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'15:34',
'15:34',
'2015-07-29, 15:34:14');
});
- test('Within 24 hours on different days', () => {
- testDates('2015-07-29 03:34:14.985000000',
+ test('Within 24 hours on different days', async () => {
+ await testDates('2015-07-29 03:34:14.985000000',
'2015-07-28 20:25:14.985000000',
'07-28',
'07-28 20:25',
'2015-07-28, 20:25:14');
});
- test('More than 24 hours but less than six months', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('More than 24 hours but less than six months', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-06-15 03:25:14.985000000',
'06-15',
'06-15 03:25',
@@ -200,24 +206,24 @@
return element._loadPreferences();
}));
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'15:34',
'15:34',
'29.07.2015, 15:34:14');
});
- test('Within 24 hours on different days', () => {
- testDates('2015-07-29 03:34:14.985000000',
+ test('Within 24 hours on different days', async () => {
+ await testDates('2015-07-29 03:34:14.985000000',
'2015-07-28 20:25:14.985000000',
'28. Jul',
'28. Jul 20:25',
'28.07.2015, 20:25:14');
});
- test('More than 24 hours but less than six months', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('More than 24 hours but less than six months', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-06-15 03:25:14.985000000',
'15. Jun',
'15. Jun 03:25',
@@ -236,24 +242,24 @@
return element._loadPreferences();
}));
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'15:34',
'15:34',
'29/07/2015, 15:34:14');
});
- test('Within 24 hours on different days', () => {
- testDates('2015-07-29 03:34:14.985000000',
+ test('Within 24 hours on different days', async () => {
+ await testDates('2015-07-29 03:34:14.985000000',
'2015-07-28 20:25:14.985000000',
'28/07',
'28/07 20:25',
'28/07/2015, 20:25:14');
});
- test('More than 24 hours but less than six months', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('More than 24 hours but less than six months', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-06-15 03:25:14.985000000',
'15/06',
'15/06 03:25',
@@ -273,8 +279,8 @@
})
);
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'3:34 PM',
'3:34 PM',
@@ -294,8 +300,8 @@
})
);
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'3:34 PM',
'3:34 PM',
@@ -315,8 +321,8 @@
})
);
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'3:34 PM',
'3:34 PM',
@@ -336,8 +342,8 @@
})
);
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'3:34 PM',
'3:34 PM',
@@ -357,8 +363,8 @@
})
);
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'3:34 PM',
'3:34 PM',
@@ -377,16 +383,16 @@
return element._loadPreferences();
}));
- test('Within 24 hours on same day', () => {
- testDates('2015-07-29 20:34:14.985000000',
+ test('Within 24 hours on same day', async () => {
+ await testDates('2015-07-29 20:34:14.985000000',
'2015-07-29 15:34:14.985000000',
'5 hours ago',
'5 hours ago',
'Jul 29, 2015, 3:34:14 PM');
});
- test('More than six months', () => {
- testDates('2015-09-15 20:34:00.000000000',
+ test('More than six months', async () => {
+ await testDates('2015-09-15 20:34:00.000000000',
'2015-01-15 03:25:00.000000000',
'8 months ago',
'8 months ago',
@@ -405,10 +411,10 @@
}));
test('Preferences are respected', () => {
- assert.equal(element._timeFormat, 'h:mm A');
- assert.equal(element._dateFormat.short, 'MM/DD');
- assert.equal(element._dateFormat.full, 'MM/DD/YY');
- assert.isTrue(element._relative);
+ assert.equal(element.timeFormat, 'h:mm A');
+ assert.equal(element.dateFormat.short, 'MM/DD');
+ assert.equal(element.dateFormat.full, 'MM/DD/YY');
+ assert.isTrue(element.relative);
});
});
@@ -419,10 +425,38 @@
}));
test('Default preferences are respected', () => {
- assert.equal(element._timeFormat, 'HH:mm');
- assert.equal(element._dateFormat.short, 'MMM DD');
- assert.equal(element._dateFormat.full, 'MMM DD, YYYY');
- assert.isFalse(element._relative);
+ assert.equal(element.timeFormat, 'HH:mm');
+ assert.equal(element.dateFormat.short, 'MMM DD');
+ assert.equal(element.dateFormat.full, 'MMM DD, YYYY');
+ assert.isFalse(element.relative);
+ });
+ });
+
+ suite('with tooltip', () => {
+ setup(async () => {
+ await stubRestAPI(null);
+ element = basicFixture.instantiate();
+ await element._loadPreferences();
+ await element.updateComplete;
+ });
+
+ test('Tooltip is present', () => {
+ const tooltip = element.shadowRoot.querySelector('gr-tooltip-content');
+ assert.isOk(tooltip);
+ });
+ });
+
+ suite('without tooltip', () => {
+ setup(async () => {
+ await stubRestAPI(null);
+ element = lightFixture.instantiate();
+ await element._loadPreferences();
+ await element.updateComplete;
+ });
+
+ test('Tooltip is absent', () => {
+ const tooltip = element.shadowRoot.querySelector('gr-tooltip-content');
+ assert.isNotOk(tooltip);
});
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
index 18a46a0..9ec1d39 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
@@ -123,6 +123,7 @@
class="dropdown-trigger"
on-click="_showDropdownTapHandler"
slot="dropdown-trigger"
+ no-uppercase
>
<span id="triggerText">[[text]]</span>
<gr-copy-clipboard
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.ts
index f1f6bf8..076553b 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_html.ts
@@ -128,7 +128,7 @@
<span class="value">[[_computeReason(change)]]</span>
<template is="dom-if" if="[[_computeLastUpdate(change)]]">
(<gr-date-formatter
- has-tooltip
+ withTooltip
date-str="[[_computeLastUpdate(change)]]"
></gr-date-formatter
>)
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.js b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.js
index e9a224c..82f64d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.js
@@ -109,7 +109,7 @@
stubRestApi('removeChangeReviewer').returns(Promise.resolve({ok: true}));
const reloadListener = sinon.spy();
element._target.addEventListener('reload', reloadListener);
- flush();
+ await flush();
const button = element.shadowRoot.querySelector('.removeReviewerOrCC');
assert.isOk(button);
assert.equal(button.innerText, 'Remove Reviewer');
@@ -132,7 +132,7 @@
const reloadListener = sinon.spy();
element._target.addEventListener('reload', reloadListener);
- flush();
+ await flush();
const button = element.shadowRoot.querySelector('.changeReviewerOrCC');
assert.isOk(button);
@@ -156,7 +156,7 @@
stubRestApi('removeChangeReviewer').returns(Promise.resolve({ok: true}));
const reloadListener = sinon.spy();
element._target.addEventListener('reload', reloadListener);
- flush();
+ await flush();
const button = element.shadowRoot.querySelector('.changeReviewerOrCC');
assert.isOk(button);
@@ -180,7 +180,7 @@
const reloadListener = sinon.spy();
element._target.addEventListener('reload', reloadListener);
- flush();
+ await flush();
const button = element.shadowRoot.querySelector('.removeReviewerOrCC');
assert.equal(button.innerText, 'Remove CC');
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.js
index 203784d..87f6052 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.js
@@ -125,7 +125,7 @@
.querySelector('[data-action-key="' + key + '"]'));
});
- test('action button properties', () => {
+ test('action button properties', async () => {
const key = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
flush();
const button = element.shadowRoot
@@ -137,17 +137,17 @@
changeActions.setTitle(key, 'Yo hint');
changeActions.setEnabled(key, false);
changeActions.setIcon(key, 'pupper');
- flush();
+ await flush();
assert.equal(button.getAttribute('data-label'), 'Yo');
- assert.equal(button.getAttribute('title'), 'Yo hint');
+ assert.equal(button.parentElement.getAttribute('title'), 'Yo hint');
assert.isTrue(button.disabled);
assert.equal(button.querySelector('iron-icon').icon,
'gr-icons:pupper');
});
- test('hide action buttons', () => {
+ test('hide action buttons', async () => {
const key = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
- flush();
+ await flush();
let button = element.shadowRoot
.querySelector('[data-action-key="' + key + '"]');
assert.isOk(button);
@@ -168,7 +168,7 @@
.querySelector('[data-action-key="' + key + '"]'));
changeActions.setActionOverflow(
changeActions.ActionType.REVISION, key, true);
- flush();
+ await flush();
assert.isNotOk(element.shadowRoot
.querySelector('[data-action-key="' + key + '"]'));
assert.isFalse(element.$.moreActions.hidden);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts
index 85491ef..1d4bd06 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.ts
@@ -34,6 +34,7 @@
import {fontStyles} from '../../../styles/gr-font-styles';
import {formStyles} from '../../../styles/gr-form-styles';
import {menuPageStyles} from '../../../styles/gr-menu-page-styles';
+import {spinnerStyles} from '../../../styles/gr-spinner-styles';
import {subpageStyles} from '../../../styles/gr-subpage-styles';
import {tableStyles} from '../../../styles/gr-table-styles';
@@ -125,6 +126,7 @@
font: fontStyles,
form: formStyles,
menuPage: menuPageStyles,
+ spinner: spinnerStyles,
subPage: subpageStyles,
table: tableStyles,
};
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
index e01c5d3..dba36a4 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
@@ -22,6 +22,7 @@
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
import '../gr-label/gr-label';
+import '../gr-tooltip-content/gr-tooltip-content';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-label-info_html';
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
index f31b57f..552bd08 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_html.ts
@@ -101,13 +101,14 @@
>
<tr class="labelValueContainer">
<td>
- <gr-label
- has-tooltip=""
+ <gr-tooltip-content
+ has-tooltip
title="[[_computeValueTooltip(labelInfo, mappedLabel.value)]]"
- class$="[[mappedLabel.className]] voteChip font-small"
>
- [[mappedLabel.value]]
- </gr-label>
+ <gr-label class$="[[mappedLabel.className]] voteChip font-small">
+ [[mappedLabel.value]]
+ </gr-label>
+ </gr-tooltip-content>
</td>
<td>
<gr-account-link
@@ -116,16 +117,17 @@
></gr-account-link>
</td>
<td>
- <gr-button
- link=""
- aria-label="Remove vote"
- on-click="_onDeleteVote"
- tooltip="Remove vote"
- data-account-id$="[[mappedLabel.account._account_id]]"
- class$="deleteBtn [[_computeDeleteClass(mappedLabel.account, mutable, change)]]"
- >
- <iron-icon icon="gr-icons:delete"></iron-icon>
- </gr-button>
+ <gr-tooltip-content has-tooltip title="Remove vote">
+ <gr-button
+ link=""
+ aria-label="Remove vote"
+ on-click="_onDeleteVote"
+ data-account-id$="[[mappedLabel.account._account_id]]"
+ class$="deleteBtn [[_computeDeleteClass(mappedLabel.account, mutable, change)]]"
+ >
+ <iron-icon icon="gr-icons:delete"></iron-icon>
+ </gr-button>
+ </gr-tooltip-content>
</td>
</tr>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts b/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
index fd10145..842b35e 100644
--- a/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
@@ -21,10 +21,8 @@
* used in gr-label-info.
*/
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {customElement} from '@polymer/decorators';
-import {htmlTemplate} from './gr-label_html';
-import {TooltipMixin} from '../../../mixins/gr-tooltip-mixin/gr-tooltip-mixin';
+import {html, LitElement} from 'lit';
+import {customElement} from 'lit/decorators';
declare global {
interface HTMLElementTagNameMap {
@@ -32,12 +30,13 @@
}
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = TooltipMixin(PolymerElement);
-
@customElement('gr-label')
-export class GrLabel extends base {
- static get template() {
- return htmlTemplate;
+export class GrLabel extends LitElement {
+ static override get styles() {
+ return [];
+ }
+
+ override render() {
+ return html` <slot></slot> `;
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
index f5a0c4d..7008db2 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.ts
@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-limited-text_html';
-import {TooltipMixin} from '../../../mixins/gr-tooltip-mixin/gr-tooltip-mixin';
-import {customElement, observe, property} from '@polymer/decorators';
+import {customElement, property} from 'lit/decorators';
+import {html, LitElement} from 'lit';
declare global {
interface HTMLElementTagNameMap {
@@ -25,9 +23,6 @@
}
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = TooltipMixin(PolymerElement);
-
/**
* The gr-limited-text element is for displaying text with a maximum length
* (in number of characters) to display. If the length of the text exceeds the
@@ -35,57 +30,56 @@
* and a tooltip containing the full text is enabled.
*/
@customElement('gr-limited-text')
-export class GrLimitedText extends base {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrLimitedText extends LitElement {
/** The un-truncated text to display. */
@property({type: String})
- text?: string;
+ text = '';
/** The maximum length for the text to display before truncating. */
@property({type: Number})
- limit?: number;
+ limit = 0;
@property({type: String})
tooltip?: string;
- /** Boolean property used by TooltipMixin. */
- @property({type: Boolean})
- override hasTooltip = false;
+ static override get styles() {
+ return [];
+ }
- /** Boolean property used by TooltipMixin. */
- @property({type: Boolean})
- disableTooltip = false;
-
- /**
- * The text or limit have changed. Recompute whether a tooltip needs to be
- * enabled.
- */
- @observe('text', 'tooltip', 'limit')
- _updateTitle(text?: string, tooltip?: string, limit?: number) {
- text = text ?? '';
- tooltip = tooltip ?? '';
- limit = limit ?? 0;
-
- this.hasTooltip = !!tooltip || (!!limit && text.length > limit);
- if (this.hasTooltip && !this.disableTooltip) {
- // Combine the text and title if over-length
- if (limit && text.length > limit) {
- this.title = `${text}${tooltip ? ` (${tooltip})` : ''}`;
- } else {
- this.title = tooltip;
- }
+ override render() {
+ if (this.tooltip || this.tooLong()) {
+ return html` <gr-tooltip-content
+ has-tooltip
+ .title=${this.renderTooltip()}
+ >
+ ${this.renderText()}
+ </gr-tooltip-content>`;
} else {
- this.title = '';
+ return this.renderText();
}
}
- _computeDisplayText(text?: string, limit?: number) {
- if (!!limit && !!text && text.length > limit) {
- return text.substr(0, limit - 1) + '…';
+ // Should be private but used in tests.
+ renderText() {
+ if (this.tooLong()) {
+ return this.text.substr(0, this.limit - 1) + '…';
}
- return text;
+ return this.text;
+ }
+
+ private renderTooltip() {
+ if (this.tooLong()) {
+ return `${this.text}${this.tooltip ? ` (${this.tooltip})` : ''}`;
+ } else if (this.tooltip) {
+ return this.tooltip;
+ } else {
+ return '';
+ }
+ }
+
+ private tooLong() {
+ if (!this.limit) return false;
+ if (!this.text) return false;
+ return this.text.length > this.limit;
}
}
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_html.ts b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_html.ts
deleted file mode 100644
index b942d07..0000000
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_html.ts
+++ /dev/null
@@ -1,19 +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` [[_computeDisplayText(text, limit)]] `;
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.js b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.js
index 3b99d6d..e3e72d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.js
@@ -23,77 +23,65 @@
suite('gr-limited-text tests', () => {
let element;
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await element.updateComplete;
});
- test('tooltip without title input', () => {
- const updateSpy = sinon.spy(element, '_updateTitle');
+ test('tooltip without title input', async () => {
element.text = 'abc 123';
- flush();
- assert.isTrue(updateSpy.calledOnce);
- assert.isNotOk(element.getAttribute('title'));
- assert.isFalse(element.hasTooltip);
+ await element.updateComplete;
+ assert.isNotOk(element.shadowRoot.querySelector('gr-tooltip-content'));
element.limit = 10;
- flush();
- assert.isTrue(updateSpy.calledTwice);
- assert.isNotOk(element.getAttribute('title'));
- assert.isFalse(element.hasTooltip);
+ await element.updateComplete;
+ assert.isNotOk(element.shadowRoot.querySelector('gr-tooltip-content'));
element.limit = 3;
- flush();
- assert.equal(updateSpy.callCount, 3);
- assert.equal(element.getAttribute('title'), 'abc 123');
- assert.equal(element.title, 'abc 123');
- assert.isTrue(element.hasTooltip);
+ await element.updateComplete;
+ assert.isOk(element.shadowRoot.querySelector('gr-tooltip-content'));
+ assert.equal(
+ element.shadowRoot.querySelector('gr-tooltip-content').title,
+ 'abc 123');
element.limit = 100;
- flush();
- assert.equal(updateSpy.callCount, 4);
- assert.isFalse(element.hasTooltip);
+ await element.updateComplete;
+ assert.isNotOk(element.shadowRoot.querySelector('gr-tooltip-content'));
element.limit = null;
- flush();
- assert.equal(updateSpy.callCount, 5);
- assert.isNotOk(element.getAttribute('title'));
- assert.isFalse(element.hasTooltip);
+ await element.updateComplete;
+ assert.isNotOk(element.shadowRoot.querySelector('gr-tooltip-content'));
});
- test('with tooltip input', () => {
- const updateSpy = sinon.spy(element, '_updateTitle');
+ test('with tooltip input', async () => {
element.tooltip = 'abc 123';
- flush();
- assert.isTrue(updateSpy.calledOnce);
- assert.isTrue(element.hasTooltip);
- assert.equal(element.getAttribute('title'), 'abc 123');
- assert.equal(element.title, 'abc 123');
+ await element.updateComplete;
+ let tooltipContent = element.shadowRoot.querySelector('gr-tooltip-content');
+ assert.isOk(tooltipContent);
+ assert.equal(tooltipContent.title, 'abc 123');
element.text = 'abc';
- flush();
- assert.equal(element.getAttribute('title'), 'abc 123');
- assert.isTrue(element.hasTooltip);
+ await element.updateComplete;
+ tooltipContent = element.shadowRoot.querySelector('gr-tooltip-content');
+ assert.isOk(tooltipContent);
+ assert.equal(tooltipContent.title, 'abc 123');
element.text = 'abcdef';
element.limit = 3;
- flush();
- assert.equal(element.getAttribute('title'), 'abcdef (abc 123)');
- assert.isTrue(element.hasTooltip);
+ await element.updateComplete;
+ tooltipContent = element.shadowRoot.querySelector('gr-tooltip-content');
+ assert.isOk(tooltipContent);
+ assert.equal(tooltipContent.title, 'abcdef (abc 123)');
});
test('_computeDisplayText', () => {
- assert.equal(element._computeDisplayText('foo bar', 100), 'foo bar');
- assert.equal(element._computeDisplayText('foo bar', 4), 'foo…');
- assert.equal(element._computeDisplayText('foo bar', null), 'foo bar');
- });
-
- test('when disable tooltip', () => {
- sinon.spy(element, '_updateTitle');
- element.text = 'abcdefghijklmn';
- element.disableTooltip = true;
- element.limit = 10;
- flush();
- assert.equal(element.getAttribute('title'), '');
+ element.text = 'foo bar';
+ element.limit = 100;
+ assert.equal(element.renderText(), 'foo bar');
+ element.limit = 4;
+ assert.equal(element.renderText(), 'foo…');
+ element.limit = 0;
+ assert.equal(element.renderText(), 'foo bar');
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
index 9c6d947..801b8bf 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.ts
@@ -87,6 +87,7 @@
override render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
+ /* eslint-disable lit/prefer-static-styles */
const customStyle = html`
<style>
gr-button::part(paper-button),
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
index 9d228ab..2b9a868 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
@@ -15,10 +15,13 @@
* limitations under the License.
*/
import '../gr-icons/gr-icons';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-tooltip-content_html';
-import {TooltipMixin} from '../../../mixins/gr-tooltip-mixin/gr-tooltip-mixin';
-import {customElement, property} from '@polymer/decorators';
+import '../gr-tooltip/gr-tooltip';
+import {getRootElement} from '../../../scripts/rootElement';
+import {GrTooltip} from '../gr-tooltip/gr-tooltip';
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+
+const BOTTOM_OFFSET = 7.2; // Height of the arrow in tooltip.
declare global {
interface HTMLElementTagNameMap {
@@ -26,21 +29,202 @@
}
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = TooltipMixin(PolymerElement);
-
-/**
- * Transclude anything inside and wrap them to support tooltip functionality.
- */
@customElement('gr-tooltip-content')
-export class GrTooltipContent extends base {
- static get template() {
- return htmlTemplate;
- }
+export class GrTooltipContent extends LitElement {
+ @property({type: Boolean, attribute: 'has-tooltip', reflect: true})
+ hasTooltip = false;
- @property({type: String, reflectToAttribute: true})
+ @property({type: Boolean, attribute: 'position-below', reflect: true})
+ positionBelow = false;
+
+ @property({type: String, attribute: 'max-width', reflect: true})
maxWidth?: string;
@property({type: Boolean})
showIcon = false;
+
+ // Should be private but used in tests.
+ @state()
+ isTouchDevice = 'ontouchstart' in document.documentElement;
+
+ // Should be private but used in tests.
+ tooltip: GrTooltip | null = null;
+
+ @state()
+ private originalTitle = '';
+
+ private hasSetupTooltipListeners = false;
+
+ private readonly windowScrollHandler: () => void;
+
+ private readonly showHandler: () => void;
+
+ private readonly hideHandler: (e: Event) => void;
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
+ constructor() {
+ super();
+ this.windowScrollHandler = () => this._handleWindowScroll();
+ this.showHandler = () => this._handleShowTooltip();
+ this.hideHandler = (e: Event | undefined) => this._handleHideTooltip(e);
+ }
+
+ override disconnectedCallback() {
+ this._handleHideTooltip(undefined);
+ this.removeEventListener('mouseenter', this.showHandler);
+ window.removeEventListener('scroll', this.windowScrollHandler);
+ super.disconnectedCallback();
+ }
+
+ static override get styles() {
+ return [
+ css`
+ iron-icon {
+ width: var(--line-height-normal);
+ height: var(--line-height-normal);
+ vertical-align: top;
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ return html`
+ <slot></slot>
+ ${this.renderIcon()}
+ `;
+ }
+
+ renderIcon() {
+ if (!this.showIcon) return;
+ return html`<iron-icon icon="gr-icons:info"></iron-icon>`;
+ }
+
+ override updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('hasTooltip')) {
+ this.setupTooltipListeners();
+ }
+ }
+
+ private setupTooltipListeners() {
+ if (!this.hasTooltip) {
+ if (this.hasSetupTooltipListeners) {
+ // if attribute set to false, remove the listener
+ this.removeEventListener('mouseenter', this.showHandler);
+ this.hasSetupTooltipListeners = false;
+ }
+ return;
+ }
+
+ if (this.hasSetupTooltipListeners) {
+ return;
+ }
+ this.hasSetupTooltipListeners = true;
+ this.addEventListener('mouseenter', this.showHandler);
+ }
+
+ _handleShowTooltip() {
+ if (this.isTouchDevice) {
+ return;
+ }
+
+ if (
+ !this.hasAttribute('title') ||
+ this.getAttribute('title') === '' ||
+ this.tooltip
+ ) {
+ return;
+ }
+
+ // Store the title attribute text then set it to an empty string to
+ // prevent it from showing natively.
+ this.originalTitle = this.getAttribute('title') || '';
+ this.setAttribute('title', '');
+
+ const tooltip = document.createElement('gr-tooltip');
+ tooltip.text = this.originalTitle;
+ tooltip.maxWidth = this.getAttribute('max-width') || '';
+ tooltip.positionBelow = this.hasAttribute('position-below');
+
+ // Set visibility to hidden before appending to the DOM so that
+ // calculations can be made based on the element’s size.
+ tooltip.style.visibility = 'hidden';
+ getRootElement().appendChild(tooltip);
+ this._positionTooltip(tooltip);
+ tooltip.style.visibility = 'initial';
+
+ this.tooltip = tooltip;
+ window.addEventListener('scroll', this.windowScrollHandler);
+ this.addEventListener('mouseleave', this.hideHandler);
+ this.addEventListener('click', this.hideHandler);
+ tooltip.addEventListener('mouseleave', this.hideHandler);
+ }
+
+ _handleHideTooltip(e: Event | undefined) {
+ if (this.isTouchDevice) {
+ return;
+ }
+ if (!this.hasAttribute('title') || !this.originalTitle) {
+ return;
+ }
+ // Do not hide if mouse left this or this.tooltip and came to this or
+ // this.tooltip
+ if (
+ (e as MouseEvent)?.relatedTarget === this.tooltip ||
+ (e as MouseEvent)?.relatedTarget === this
+ ) {
+ return;
+ }
+
+ window.removeEventListener('scroll', this.windowScrollHandler);
+ this.removeEventListener('mouseleave', this.hideHandler);
+ this.removeEventListener('click', this.hideHandler);
+ this.setAttribute('title', this.originalTitle);
+ this.tooltip?.removeEventListener('mouseleave', this.hideHandler);
+
+ if (this.tooltip?.parentNode) {
+ this.tooltip.parentNode.removeChild(this.tooltip);
+ }
+ this.tooltip = null;
+ }
+
+ _handleWindowScroll() {
+ if (!this.tooltip) {
+ return;
+ }
+ // This wait is needed for tooltips to be positioned correctly in Firefox
+ // and Safari.
+ this.updateComplete.then(() => this._positionTooltip(this.tooltip));
+ }
+
+ // private but used in tests.
+ async _positionTooltip(tooltip: GrTooltip | null) {
+ if (tooltip === null) return;
+ const rect = this.getBoundingClientRect();
+ const boxRect = tooltip.getBoundingClientRect();
+ if (!tooltip.parentElement) {
+ return;
+ }
+ const parentRect = tooltip.parentElement.getBoundingClientRect();
+ const top = rect.top - parentRect.top;
+ const left = rect.left - parentRect.left + (rect.width - boxRect.width) / 2;
+ const right = parentRect.width - left - boxRect.width;
+ if (left < 0) {
+ tooltip.updateStyles({
+ '--gr-tooltip-arrow-center-offset': `${left}px`,
+ });
+ } else if (right < 0) {
+ tooltip.updateStyles({
+ '--gr-tooltip-arrow-center-offset': `${-0.5 * right}px`,
+ });
+ }
+ tooltip.style.left = `${Math.max(0, left)}px`;
+
+ if (!this.positionBelow) {
+ tooltip.style.top = `${Math.max(0, top)}px`;
+ tooltip.style.transform = `translateY(calc(-100% - ${BOTTOM_OFFSET}px))`;
+ } else {
+ tooltip.style.top = `${top + rect.height + BOTTOM_OFFSET}px`;
+ }
+ }
}
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_html.ts b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_html.ts
deleted file mode 100644
index 952420d..0000000
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_html.ts
+++ /dev/null
@@ -1,30 +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>
- iron-icon {
- width: var(--line-height-normal);
- height: var(--line-height-normal);
- vertical-align: top;
- }
- </style>
- <slot></slot
- ><!--
- --><iron-icon icon="gr-icons:info" hidden$="[[!showIcon]]"></iron-icon>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.js b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.js
index f905eaa..8d3bbb0 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.js
@@ -17,35 +17,162 @@
import '../../../test/common-test-setup-karma.js';
import './gr-tooltip-content.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
-import {html} from '@polymer/polymer/lib/utils/html-tag.js';
-const basicFixture = fixtureFromTemplate(html`
-<gr-tooltip-content>
- </gr-tooltip-content>
-`);
+const basicFixture = fixtureFromElement('gr-tooltip-content');
suite('gr-tooltip-content tests', () => {
let element;
- setup(() => {
+
+ function makeTooltip(tooltipRect, parentRect) {
+ return {
+ getBoundingClientRect() { return tooltipRect; },
+ updateStyles: sinon.stub(),
+ style: {left: 0, top: 0},
+ parentElement: {
+ getBoundingClientRect() { return parentRect; },
+ },
+ };
+ }
+
+ setup(async () => {
element = basicFixture.instantiate();
+ element.title = 'title';
+ await element.updateComplete;
});
test('icon is not visible by default', () => {
- assert.equal(dom(element.root)
- .querySelector('iron-icon').hidden, true);
+ assert.isNotOk(element.shadowRoot.querySelector('iron-icon'));
});
- test('position-below attribute is reflected', () => {
+ test('icon is visible with showIcon property', async () => {
+ element.showIcon = true;
+ await element.updateComplete;
+ assert.isOk(element.shadowRoot.querySelector('iron-icon'));
+ });
+
+ test('position-below attribute is reflected', async () => {
assert.isFalse(element.hasAttribute('position-below'));
element.positionBelow = true;
+ await element.updateComplete;
assert.isTrue(element.hasAttribute('position-below'));
});
- test('icon is visible with showIcon property', () => {
- element.showIcon = true;
- assert.equal(dom(element.root)
- .querySelector('iron-icon').hidden, false);
+ test('normal position', () => {
+ sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
+ return {top: 100, left: 100, width: 200};
+ });
+ const tooltip = makeTooltip(
+ {height: 30, width: 50},
+ {top: 0, left: 0, width: 1000});
+
+ element._positionTooltip(tooltip);
+ assert.isFalse(tooltip.updateStyles.called);
+ assert.equal(tooltip.style.left, '175px');
+ assert.equal(tooltip.style.top, '100px');
+ });
+
+ test('left side position', () => {
+ sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
+ return {top: 100, left: 10, width: 50};
+ });
+ const tooltip = makeTooltip(
+ {height: 30, width: 120},
+ {top: 0, left: 0, width: 1000});
+
+ element._positionTooltip(tooltip);
+ assert.isTrue(tooltip.updateStyles.called);
+ const offset = tooltip.updateStyles
+ .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
+ assert.isBelow(parseFloat(offset.replace(/px$/, '')), 0);
+ assert.equal(tooltip.style.left, '0px');
+ assert.equal(tooltip.style.top, '100px');
+ });
+
+ test('right side position', () => {
+ sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
+ return {top: 100, left: 950, width: 50};
+ });
+ const tooltip = makeTooltip(
+ {height: 30, width: 120},
+ {top: 0, left: 0, width: 1000});
+
+ element._positionTooltip(tooltip);
+ assert.isTrue(tooltip.updateStyles.called);
+ const offset = tooltip.updateStyles
+ .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
+ assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
+ assert.equal(tooltip.style.left, '915px');
+ assert.equal(tooltip.style.top, '100px');
+ });
+
+ test('position to bottom', () => {
+ sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
+ return {top: 100, left: 950, width: 50, height: 50};
+ });
+ const tooltip = makeTooltip(
+ {height: 30, width: 120},
+ {top: 0, left: 0, width: 1000});
+
+ element.positionBelow = true;
+ element._positionTooltip(tooltip);
+ assert.isTrue(tooltip.updateStyles.called);
+ const offset = tooltip.updateStyles
+ .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
+ assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
+ assert.equal(tooltip.style.left, '915px');
+ assert.equal(tooltip.style.top, '157.2px');
+ });
+
+ test('hides tooltip when detached', async () => {
+ sinon.stub(element, '_handleHideTooltip');
+ element.remove();
+ await element.updateComplete;
+ assert.isTrue(element._handleHideTooltip.called);
+ });
+
+ test('sets up listeners when has-tooltip is changed', async () => {
+ const addListenerStub = sinon.stub(element, 'addEventListener');
+ element.hasTooltip = true;
+ await element.updateComplete;
+ assert.isTrue(addListenerStub.called);
+ });
+
+ test('clean up listeners when has-tooltip changed to false', async () => {
+ const removeListenerStub = sinon.stub(element, 'removeEventListener');
+ element.hasTooltip = true;
+ await element.updateComplete;
+ element.hasTooltip = false;
+ await element.updateComplete;
+ assert.isTrue(removeListenerStub.called);
+ });
+
+ test('do not display tooltips on touch devices', async () => {
+ // On touch devices, tooltips should not be shown.
+ element.isTouchDevice = true;
+ await element.updateComplete;
+
+ // fire mouse-enter
+ element._handleShowTooltip();
+ await element.updateComplete;
+ assert.isNotOk(element.tooltip);
+
+ // fire mouse-enter
+ element._handleHideTooltip();
+ await element.updateComplete;
+ assert.isNotOk(element.tooltip);
+
+ // On other devices, tooltips should be shown.
+ element.isTouchDevice = false;
+
+ // fire mouse-enter
+ element._handleShowTooltip();
+ await element.updateComplete;
+ assert.isOk(element.tooltip);
+
+ // fire mouse-enter
+ element._handleHideTooltip();
+ await element.updateComplete;
+ assert.isNotOk(element.tooltip);
});
});
diff --git a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin.ts b/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin.ts
deleted file mode 100644
index 9e4608f..0000000
--- a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 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 {PolymerElement} from '@polymer/polymer';
-import {Constructor} from '../../utils/common-util';
-import {property} from '@polymer/decorators';
-import {ServerInfo} from '../../types/common';
-
-/**
- * @polymer
- * @mixinFunction
- */
-export const ChangeTableMixin = <T extends Constructor<PolymerElement>>(
- superClass: T
-) => {
- /**
- * @polymer
- * @mixinClass
- */
- class Mixin extends superClass {
- @property({type: Array})
- readonly columnNames: string[] = [
- 'Subject',
- 'Status',
- 'Owner',
- 'Assignee',
- 'Reviewers',
- 'Comments',
- 'Repo',
- 'Branch',
- 'Updated',
- 'Size',
- ];
-
- isColumnHidden(columnToCheck?: string, columnsToDisplay?: string[]) {
- if (!columnsToDisplay || !columnToCheck) {
- return false;
- }
- return !columnsToDisplay.includes(columnToCheck);
- }
-
- /**
- * Is the column disabled by a server config or experiment? For example the
- * assignee feature might be disabled and thus the corresponding column is
- * also disabled.
- *
- */
- isColumnEnabled(column: string, config: ServerInfo, experiments: string[]) {
- if (!config || !config.change) return true;
- if (column === 'Assignee') return !!config.change.enable_assignee;
- if (column === 'Comments') return experiments.includes('comments-column');
- return true;
- }
-
- /**
- * @return enabled columns, see isColumnEnabled().
- */
- getEnabledColumns(
- columns: string[],
- config: ServerInfo,
- experiments: string[]
- ) {
- return columns.filter(col =>
- this.isColumnEnabled(col, config, experiments)
- );
- }
-
- /**
- * The Project column was renamed to Repo, but some users may have
- * preferences that use its old name. If that column is found, rename it
- * before use.
- *
- * @return If the column was renamed, returns a new array
- * with the corrected name. Otherwise, it returns the original param.
- */
- renameProjectToRepoColumn(columns: string[]) {
- const projectIndex = columns.indexOf('Project');
- if (projectIndex === -1) {
- return columns;
- }
- const newColumns = [...columns];
- newColumns[projectIndex] = 'Repo';
- return newColumns;
- }
- }
-
- return Mixin as T & Constructor<ChangeTableMixinInterface>;
-};
-
-export interface ChangeTableMixinInterface {
- readonly columnNames: string[];
- isColumnHidden(columnToCheck?: string, columnsToDisplay?: string[]): boolean;
- isColumnEnabled(
- column: string,
- config: ServerInfo,
- experiments: string[]
- ): boolean;
- getEnabledColumns(
- columns: string[],
- config: ServerInfo,
- experiments: string[]
- ): string[];
- renameProjectToRepoColumn(columns: string[]): string[];
-}
diff --git a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js b/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js
deleted file mode 100644
index 8bc223f..0000000
--- a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 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 '../../test/common-test-setup-karma.js';
-import {ChangeTableMixin} from './gr-change-table-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(PolymerElement);
-
-class GrChangeTableMixinTestElement extends base {
- static get is() { return 'gr-change-table-mixin-test-element'; }
-}
-
-customElements.define(GrChangeTableMixinTestElement.is,
- GrChangeTableMixinTestElement);
-
-const basicFixture = fixtureFromElement(
- 'gr-change-table-mixin-test-element');
-
-suite('gr-change-table-mixin tests', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
- });
-
- test('isColumnHidden', () => {
- const columnToCheck = 'Repo';
- let columnsToDisplay = [
- 'Subject',
- 'Status',
- 'Owner',
- 'Assignee',
- 'Repo',
- 'Branch',
- 'Updated',
- 'Size',
- ];
- assert.isFalse(element.isColumnHidden(columnToCheck, columnsToDisplay));
-
- columnsToDisplay = [
- 'Subject',
- 'Status',
- 'Owner',
- 'Assignee',
- 'Branch',
- 'Updated',
- 'Size',
- ];
- assert.isTrue(element.isColumnHidden(columnToCheck, columnsToDisplay));
- });
-
- test('renameProjectToRepoColumn maps Project to Repo', () => {
- const columns = [
- 'Subject',
- 'Status',
- 'Owner',
- ];
- assert.deepEqual(element.renameProjectToRepoColumn(columns),
- columns.slice(0));
- assert.deepEqual(
- element.renameProjectToRepoColumn(columns.concat(['Project'])),
- columns.slice(0).concat(['Repo']));
- });
-});
-
diff --git a/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin.ts b/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin.ts
deleted file mode 100644
index 70d212c..0000000
--- a/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {encodeURL, getBaseUrl} from '../../utils/url-util';
-import {PolymerElement} from '@polymer/polymer';
-import {Constructor} from '../../utils/common-util';
-
-/**
- * @polymer
- * @mixinFunction
- */
-export const ListViewMixin = <T extends Constructor<PolymerElement>>(
- superClass: T
-) => {
- /**
- * @polymer
- * @mixinClass
- */
- class Mixin extends superClass {
- computeLoadingClass(loading: boolean): string {
- return loading ? 'loading' : '';
- }
-
- computeShownItems<T>(items: T[]): T[] {
- return items.slice(0, 25);
- }
-
- getUrl(path: string, item: string) {
- return getBaseUrl() + path + encodeURL(item, true);
- }
-
- getFilterValue<T extends ListViewParams>(params: T): string {
- if (!params) {
- return '';
- }
- return params.filter || '';
- }
-
- getOffsetValue<T extends ListViewParams>(params: T): number {
- if (params?.offset) {
- return Number(params.offset);
- }
- return 0;
- }
- }
-
- return Mixin as T & Constructor<ListViewMixinInterface>;
-};
-
-export interface ListViewMixinInterface {
- computeLoadingClass(loading: boolean): string;
- computeShownItems<T>(items: T[]): T[];
- getUrl(path: string, item: string): string;
- getFilterValue<T extends ListViewParams>(params: T): string;
- getOffsetValue<T extends ListViewParams>(params: T): number;
-}
-
-export interface ListViewParams {
- filter?: string | null;
- offset?: number | string;
-}
diff --git a/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin_test.js b/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin_test.js
deleted file mode 100644
index d2b429f..0000000
--- a/polygerrit-ui/app/mixins/gr-list-view-mixin/gr-list-view-mixin_test.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import '../../test/common-test-setup-karma.js';
-import {ListViewMixin} from './gr-list-view-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-
-const basicFixture = fixtureFromElement(
- 'gr-list-view-mixin-test-element');
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ListViewMixin(PolymerElement);
-
-class GrListViewMixinTestElement extends base {
- static get is() { return 'gr-list-view-mixin-test-element'; }
-}
-
-customElements.define(GrListViewMixinTestElement.is,
- GrListViewMixinTestElement);
-
-suite('gr-list-view-mixin tests', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
- });
-
- test('computeLoadingClass', () => {
- assert.equal(element.computeLoadingClass(true), 'loading');
- assert.equal(element.computeLoadingClass(false), '');
- });
-
- test('computeShownItems', () => {
- const myArr = new Array(26);
- assert.equal(element.computeShownItems(myArr).length, 25);
- });
-
- test('getUrl', () => {
- assert.equal(element.getUrl('/path/to/something/', 'item'),
- '/path/to/something/item');
- assert.equal(element.getUrl('/path/to/something/', 'item%test'),
- '/path/to/something/item%2525test');
- });
-
- test('getFilterValue', () => {
- let params;
- assert.equal(element.getFilterValue(params), '');
-
- params = {filter: null};
- assert.equal(element.getFilterValue(params), '');
-
- params = {filter: 'test'};
- assert.equal(element.getFilterValue(params), 'test');
- });
-
- test('getOffsetValue', () => {
- let params;
- assert.equal(element.getOffsetValue(params), 0);
-
- params = {offset: null};
- assert.equal(element.getOffsetValue(params), 0);
-
- params = {offset: 1};
- assert.equal(element.getOffsetValue(params), 1);
- });
-});
-
diff --git a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts b/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts
deleted file mode 100644
index 3e20d1d..0000000
--- a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin.ts
+++ /dev/null
@@ -1,227 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 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 '../../elements/shared/gr-tooltip/gr-tooltip';
-import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {getRootElement} from '../../scripts/rootElement';
-import {property, observe} from '@polymer/decorators';
-import {GrTooltip} from '../../elements/shared/gr-tooltip/gr-tooltip';
-import {PolymerElement} from '@polymer/polymer';
-import {Constructor} from '../../utils/common-util';
-
-const BOTTOM_OFFSET = 7.2; // Height of the arrow in tooltip.
-
-/** The interface corresponding to TooltipMixin */
-export interface TooltipMixinInterface {
- hasTooltip: boolean;
- positionBelow: boolean;
- _isTouchDevice: boolean;
- _tooltip: GrTooltip | null;
- _titleText: string;
- _hasSetupTooltipListeners: boolean;
-}
-
-/**
- * @polymer
- * @mixinFunction
- */
-export const TooltipMixin = <T extends Constructor<PolymerElement>>(
- superClass: T
-) => {
- /**
- * @polymer
- * @mixinClass
- */
- class Mixin extends superClass {
- @property({type: Boolean})
- hasTooltip = false;
-
- @property({type: Boolean, reflectToAttribute: true})
- positionBelow = false;
-
- @property({type: Boolean})
- _isTouchDevice = 'ontouchstart' in document.documentElement;
-
- @property({type: Object})
- _tooltip: GrTooltip | null = null;
-
- @property({type: String})
- _titleText = '';
-
- @property({type: Boolean})
- _hasSetupTooltipListeners = false;
-
- // Handler for mouseenter event
- private mouseenterHandler?: (e: MouseEvent) => void;
-
- // Handler for scrolling on window
- private readonly windowScrollHandler: () => void;
-
- // Handler for showing the tooltip, will be attached to certain events
- private readonly showHandler: () => void;
-
- // Handler for hiding the tooltip, will be attached to certain events
- private readonly hideHandler: (e: Event) => void;
-
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
- constructor(..._: any[]) {
- super();
- this.windowScrollHandler = () => this._handleWindowScroll();
- this.showHandler = () => this._handleShowTooltip();
- this.hideHandler = (e: Event | undefined) => this._handleHideTooltip(e);
- }
-
- override disconnectedCallback() {
- // NOTE: if you define your own `detached` in your component
- // then this won't take affect (as its not a class yet)
- this._handleHideTooltip(undefined);
- if (this.mouseenterHandler) {
- this.removeEventListener('mouseenter', this.mouseenterHandler);
- }
- window.removeEventListener('scroll', this.windowScrollHandler);
- super.disconnectedCallback();
- }
-
- @observe('hasTooltip')
- _setupTooltipListeners() {
- if (!this.mouseenterHandler) {
- this.mouseenterHandler = this.showHandler;
- }
-
- if (!this.hasTooltip) {
- // if attribute set to false, remove the listener
- this.removeEventListener('mouseenter', this.mouseenterHandler);
- this._hasSetupTooltipListeners = false;
- return;
- }
-
- if (this._hasSetupTooltipListeners) {
- return;
- }
- this._hasSetupTooltipListeners = true;
-
- this.addEventListener('mouseenter', this.mouseenterHandler);
- }
-
- _handleShowTooltip() {
- if (this._isTouchDevice) {
- return;
- }
-
- if (
- !this.hasAttribute('title') ||
- this.getAttribute('title') === '' ||
- this._tooltip
- ) {
- return;
- }
-
- // Store the title attribute text then set it to an empty string to
- // prevent it from showing natively.
- this._titleText = this.getAttribute('title') || '';
- this.setAttribute('title', '');
-
- const tooltip = document.createElement('gr-tooltip');
- tooltip.text = this._titleText;
- tooltip.maxWidth = this.getAttribute('max-width') || '';
- tooltip.positionBelow = this.hasAttribute('position-below');
-
- // Set visibility to hidden before appending to the DOM so that
- // calculations can be made based on the element’s size.
- tooltip.style.visibility = 'hidden';
- getRootElement().appendChild(tooltip);
- this._positionTooltip(tooltip);
- tooltip.style.visibility = 'initial';
-
- this._tooltip = tooltip;
- window.addEventListener('scroll', this.windowScrollHandler);
- this.addEventListener('mouseleave', this.hideHandler);
- this.addEventListener('click', this.hideHandler);
- tooltip.addEventListener('mouseleave', this.hideHandler);
- }
-
- _handleHideTooltip(e: Event | undefined) {
- if (this._isTouchDevice) {
- return;
- }
- if (!this.hasAttribute('title') || !this._titleText) {
- return;
- }
- // Do not hide if mouse left this or this._tooltip and came to this or
- // this._tooltip
- if (
- (e as MouseEvent)?.relatedTarget === this._tooltip ||
- (e as MouseEvent)?.relatedTarget === this
- ) {
- return;
- }
-
- window.removeEventListener('scroll', this.windowScrollHandler);
- this.removeEventListener('mouseleave', this.hideHandler);
- this.removeEventListener('click', this.hideHandler);
- this.setAttribute('title', this._titleText);
- this._tooltip?.removeEventListener('mouseleave', this.hideHandler);
-
- if (this._tooltip?.parentNode) {
- this._tooltip.parentNode.removeChild(this._tooltip);
- }
- this._tooltip = null;
- }
-
- _handleWindowScroll() {
- if (!this._tooltip) {
- return;
- }
-
- this._positionTooltip(this._tooltip);
- }
-
- _positionTooltip(tooltip: GrTooltip) {
- // This flush is needed for tooltips to be positioned correctly in Firefox
- // and Safari.
- flush();
- const rect = this.getBoundingClientRect();
- const boxRect = tooltip.getBoundingClientRect();
- if (!tooltip.parentElement) {
- return;
- }
- const parentRect = tooltip.parentElement.getBoundingClientRect();
- const top = rect.top - parentRect.top;
- const left =
- rect.left - parentRect.left + (rect.width - boxRect.width) / 2;
- const right = parentRect.width - left - boxRect.width;
- if (left < 0) {
- tooltip.updateStyles({
- '--gr-tooltip-arrow-center-offset': `${left}px`,
- });
- } else if (right < 0) {
- tooltip.updateStyles({
- '--gr-tooltip-arrow-center-offset': `${-0.5 * right}px`,
- });
- }
- tooltip.style.left = `${Math.max(0, left)}px`;
-
- if (!this.positionBelow) {
- tooltip.style.top = `${Math.max(0, top)}px`;
- tooltip.style.transform = `translateY(calc(-100% - ${BOTTOM_OFFSET}px))`;
- } else {
- tooltip.style.top = `${top + rect.height + BOTTOM_OFFSET}px`;
- }
- }
- }
-
- return Mixin as T & Constructor<TooltipMixinInterface>;
-};
diff --git a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin_test.js b/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin_test.js
deleted file mode 100644
index 69e6e86..0000000
--- a/polygerrit-ui/app/mixins/gr-tooltip-mixin/gr-tooltip-mixin_test.js
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../test/common-test-setup-karma.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {TooltipMixin} from './gr-tooltip-mixin.js';
-
-const basicFixture = fixtureFromElement('gr-tooltip-mixin-element');
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = TooltipMixin(PolymerElement);
-
-class GrTooltipMixinTestElement extends base {
- static get is() {
- return 'gr-tooltip-mixin-element';
- }
-}
-
-customElements.define(GrTooltipMixinTestElement.is,
- GrTooltipMixinTestElement);
-
-suite('gr-tooltip-mixin tests', () => {
- let element;
-
- function makeTooltip(tooltipRect, parentRect) {
- return {
- getBoundingClientRect() { return tooltipRect; },
- updateStyles: sinon.stub(),
- style: {left: 0, top: 0},
- parentElement: {
- getBoundingClientRect() { return parentRect; },
- },
- };
- }
-
- setup(() => {
- element = basicFixture.instantiate();
- });
-
- test('normal position', () => {
- sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
- return {top: 100, left: 100, width: 200};
- });
- const tooltip = makeTooltip(
- {height: 30, width: 50},
- {top: 0, left: 0, width: 1000});
-
- element._positionTooltip(tooltip);
- assert.isFalse(tooltip.updateStyles.called);
- assert.equal(tooltip.style.left, '175px');
- assert.equal(tooltip.style.top, '100px');
- });
-
- test('left side position', () => {
- sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
- return {top: 100, left: 10, width: 50};
- });
- const tooltip = makeTooltip(
- {height: 30, width: 120},
- {top: 0, left: 0, width: 1000});
-
- element._positionTooltip(tooltip);
- assert.isTrue(tooltip.updateStyles.called);
- const offset = tooltip.updateStyles
- .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
- assert.isBelow(parseFloat(offset.replace(/px$/, '')), 0);
- assert.equal(tooltip.style.left, '0px');
- assert.equal(tooltip.style.top, '100px');
- });
-
- test('right side position', () => {
- sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
- return {top: 100, left: 950, width: 50};
- });
- const tooltip = makeTooltip(
- {height: 30, width: 120},
- {top: 0, left: 0, width: 1000});
-
- element._positionTooltip(tooltip);
- assert.isTrue(tooltip.updateStyles.called);
- const offset = tooltip.updateStyles
- .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
- assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
- assert.equal(tooltip.style.left, '915px');
- assert.equal(tooltip.style.top, '100px');
- });
-
- test('position to bottom', () => {
- sinon.stub(element, 'getBoundingClientRect').callsFake(() => {
- return {top: 100, left: 950, width: 50, height: 50};
- });
- const tooltip = makeTooltip(
- {height: 30, width: 120},
- {top: 0, left: 0, width: 1000});
-
- element.positionBelow = true;
- element._positionTooltip(tooltip);
- assert.isTrue(tooltip.updateStyles.called);
- const offset = tooltip.updateStyles
- .lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
- assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
- assert.equal(tooltip.style.left, '915px');
- assert.equal(tooltip.style.top, '157.2px');
- });
-
- test('hides tooltip when detached', () => {
- sinon.stub(element, '_handleHideTooltip');
- element.remove();
- flush();
- assert.isTrue(element._handleHideTooltip.called);
- });
-
- test('sets up listeners when has-tooltip is changed', () => {
- const addListenerStub = sinon.stub(element, 'addEventListener');
- element.hasTooltip = true;
- assert.isTrue(addListenerStub.called);
- });
-
- test('clean up listeners when has-tooltip changed to false', () => {
- const removeListenerStub = sinon.stub(element, 'removeEventListener');
- element.hasTooltip = true;
- element.hasTooltip = false;
- assert.isTrue(removeListenerStub.called);
- });
-});
-
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
index 5c4c9ce..9092919 100644
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
+++ b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
@@ -151,6 +151,7 @@
TOGGLE_CHANGE_STAR = 'TOGGLE_CHANGE_STAR',
REFRESH_CHANGE_LIST = 'REFRESH_CHANGE_LIST',
OPEN_SUBMIT_DIALOG = 'OPEN_SUBMIT_DIALOG',
+ TOGGLE_ATTENTION_SET = 'TOGGLE_ATTENTION_SET',
OPEN_REPLY_DIALOG = 'OPEN_REPLY_DIALOG',
OPEN_DOWNLOAD_DIALOG = 'OPEN_DOWNLOAD_DIALOG',
@@ -334,6 +335,11 @@
ShortcutSection.ACTIONS,
'Open submit dialog'
);
+_describe(
+ Shortcut.TOGGLE_ATTENTION_SET,
+ ShortcutSection.ACTIONS,
+ 'Toggle attention set status'
+);
_describe(Shortcut.EDIT_TOPIC, ShortcutSection.ACTIONS, 'Add a change topic');
_describe(
Shortcut.DIFF_AGAINST_BASE,
diff --git a/polygerrit-ui/app/styles/gr-a11y-styles.ts b/polygerrit-ui/app/styles/gr-a11y-styles.ts
new file mode 100644
index 0000000..a1fa62b
--- /dev/null
+++ b/polygerrit-ui/app/styles/gr-a11y-styles.ts
@@ -0,0 +1,42 @@
+/**
+ * @license
+ * Copyright (C) 2021 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 {css} from 'lit';
+
+export const a11yStyles = css`
+ .assistive-tech-only {
+ user-select: none;
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ margin: 0;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+ z-index: -1000;
+ }
+`;
+
+const $_documentContainer = document.createElement('template');
+$_documentContainer.innerHTML = `<dom-module id="gr-a11y-styles">
+ <template>
+ <style>
+ ${a11yStyles.cssText}
+ </style>
+ </template>
+</dom-module>`;
+document.head.appendChild($_documentContainer.content);
diff --git a/polygerrit-ui/app/styles/gr-spinner-styles.ts b/polygerrit-ui/app/styles/gr-spinner-styles.ts
index 0f8341f..6015be4 100644
--- a/polygerrit-ui/app/styles/gr-spinner-styles.ts
+++ b/polygerrit-ui/app/styles/gr-spinner-styles.ts
@@ -16,13 +16,6 @@
*/
import {css} from 'lit';
-// Mark the file as a module. Otherwise typescript assumes this is a script
-// and $_documentContainer is a global variable.
-// See: https://www.typescriptlang.org/docs/handbook/modules.html
-export {};
-
-const $_documentContainer = document.createElement('template');
-
export const spinnerStyles = css`
.loadingSpin {
border: 2px solid var(--disabled-button-background-color);
@@ -43,6 +36,7 @@
}
`;
+const $_documentContainer = document.createElement('template');
$_documentContainer.innerHTML = `<dom-module id="gr-spinner-styles">
<template>
<style>
diff --git a/polygerrit-ui/app/styles/gr-voting-styles.ts b/polygerrit-ui/app/styles/gr-voting-styles.ts
index 12d0784..a623d99 100644
--- a/polygerrit-ui/app/styles/gr-voting-styles.ts
+++ b/polygerrit-ui/app/styles/gr-voting-styles.ts
@@ -18,24 +18,26 @@
// Mark the file as a module. Otherwise typescript assumes this is a script
// and $_documentContainer is a global variable.
// See: https://www.typescriptlang.org/docs/handbook/modules.html
-export {};
+import {css} from 'lit';
+
+export const votingStyles = css`
+ .voteChip {
+ border: 1px solid var(--border-color);
+ /* max rounded */
+ border-radius: 1em;
+ box-shadow: none;
+ box-sizing: border-box;
+ min-width: 3em;
+ color: var(--vote-text-color);
+ }
+`;
const $_documentContainer = document.createElement('template');
-
$_documentContainer.innerHTML = `<dom-module id="gr-voting-styles">
<template>
<style>
- .voteChip {
- border: 1px solid var(--border-color);
- /* max rounded */
- border-radius: 1em;
- box-shadow: none;
- box-sizing: border-box;
- min-width: 3em;
- color: var(--vote-text-color);
- }
+ ${votingStyles.cssText}
</style>
</template>
</dom-module>`;
-
document.head.appendChild($_documentContainer.content);
diff --git a/polygerrit-ui/app/styles/shared-styles.ts b/polygerrit-ui/app/styles/shared-styles.ts
index b58d5b9..98f6eb2 100644
--- a/polygerrit-ui/app/styles/shared-styles.ts
+++ b/polygerrit-ui/app/styles/shared-styles.ts
@@ -224,19 +224,6 @@
--iron-autogrow-textarea_-_white-space: pre-wrap;
}
- .assistive-tech-only {
- user-select: none;
- clip: rect(1px, 1px, 1px, 1px);
- height: 1px;
- margin: 0;
- overflow: hidden;
- padding: 0;
- position: absolute;
- white-space: nowrap;
- width: 1px;
- z-index: -1000;
- }
-
/**
* TODO: Remove these rules and change (plugin) users to rely on
* gr-spinner-styles directly.
diff --git a/polygerrit-ui/app/utils/attention-set-util.ts b/polygerrit-ui/app/utils/attention-set-util.ts
index 631b633..dcd2863 100644
--- a/polygerrit-ui/app/utils/attention-set-util.ts
+++ b/polygerrit-ui/app/utils/attention-set-util.ts
@@ -16,6 +16,7 @@
*/
import {AccountInfo, ChangeInfo, ServerInfo} from '../types/common';
+import {ParsedChangeInfo} from '../types/types';
import {
getAccountTemplate,
isServiceUser,
@@ -29,7 +30,7 @@
export function hasAttention(
account?: AccountInfo,
- change?: ChangeInfo
+ change?: ChangeInfo | ParsedChangeInfo
): boolean {
return (
canHaveAttention(account) &&
@@ -41,7 +42,7 @@
export function getReason(
config?: ServerInfo,
account?: AccountInfo,
- change?: ChangeInfo
+ change?: ChangeInfo | ParsedChangeInfo
) {
if (!hasAttention(account, change)) return '';
if (change?.attention_set === undefined) return '';
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index c94493b..278e7f3 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -215,7 +215,7 @@
}
export function isUploader(
- change?: ChangeInfo,
+ change?: ChangeInfo | ParsedChangeInfo,
account?: AccountInfo
): boolean {
if (!change || !account) return false;
@@ -224,7 +224,7 @@
}
export function isInvolved(
- change?: ChangeInfo,
+ change?: ChangeInfo | ParsedChangeInfo,
account?: AccountInfo
): boolean {
const owner = isOwner(change, account);
diff --git a/polygerrit-ui/app/utils/common-util.ts b/polygerrit-ui/app/utils/common-util.ts
index 5370cf9..0002254 100644
--- a/polygerrit-ui/app/utils/common-util.ts
+++ b/polygerrit-ui/app/utils/common-util.ts
@@ -100,7 +100,7 @@
}
export function query<E extends Element = Element>(
- el: Element | undefined,
+ el: Element | null | undefined,
selector: string
): E | undefined {
if (!el) return undefined;
@@ -109,7 +109,7 @@
}
export function queryAndAssert<E extends Element = Element>(
- el: Element | undefined,
+ el: Element | null | undefined,
selector: string
): E {
const found = query<E>(el, selector);
diff --git a/polygerrit-ui/app/utils/dom-util.ts b/polygerrit-ui/app/utils/dom-util.ts
index 16129af..7b1f3e3 100644
--- a/polygerrit-ui/app/utils/dom-util.ts
+++ b/polygerrit-ui/app/utils/dom-util.ts
@@ -171,7 +171,7 @@
* getEventPath(e); // eg: div.class1>p#pid.class2
* }
*/
-export function getEventPath<T extends PolymerEvent>(e?: T) {
+export function getEventPath<T extends MouseEvent>(e?: T) {
if (!e) return '';
let path = e.composedPath();
diff --git a/yarn.lock b/yarn.lock
index be412a5..17c08c3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -954,6 +954,15 @@
semver "^7.3.5"
spdx-expression-parse "^3.0.1"
+eslint-plugin-lit@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-lit/-/eslint-plugin-lit-1.5.1.tgz#e5b86fee4aeb6023ad4bb90b3d9e462ca8eff755"
+ integrity sha512-pYB0QM11uyOk5L55QfGhBmWi8a56PkNsnx+zVpY4bxz9YVquEo4BeRnFmf9AwFyT89rhGud9QruFhM2xJ4piwg==
+ dependencies:
+ parse5 "^6.0.1"
+ parse5-htmlparser2-tree-adapter "^6.0.1"
+ requireindex "^1.2.0"
+
eslint-plugin-node@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
@@ -2145,6 +2154,13 @@
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
+parse5-htmlparser2-tree-adapter@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
+ integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
+ dependencies:
+ parse5 "^6.0.1"
+
parse5@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
@@ -2152,6 +2168,11 @@
dependencies:
"@types/node" "*"
+parse5@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
+ integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
+
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
@@ -2386,6 +2407,11 @@
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+requireindex@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef"
+ integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==
+
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"