summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaladox none <thomasmulhall410@yahoo.com>2022-05-12 12:17:32 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2022-05-12 12:17:32 +0000
commit94d0f99fa2d0373af5083068d49c6f4287135065 (patch)
treef50658737639afcefc436fbfddc8e62b0e10d1fa
parenta792772864797a2b6db782cc711b02cac2e6fb98 (diff)
parent814ac33d352a22c56e4126598fc0b61ae5e369df (diff)
Merge changes Id116e2b4,Ibdc1c20d,I3de8ec70 into stable-3.6
* changes: Fix issue with closing comment Finish rendering a message before scrolling Migrate gr-message to Lit
-rw-r--r--polygerrit-ui/app/elements/change/gr-message/gr-message.ts607
-rw-r--r--polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts307
-rw-r--r--polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts329
-rw-r--r--polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts22
-rw-r--r--polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts51
-rw-r--r--polygerrit-ui/app/utils/comment-util.ts7
6 files changed, 691 insertions, 632 deletions
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index 8664de32e7..0902fe6e5f 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -21,12 +21,10 @@ import '../../shared/gr-account-chip/gr-account-chip';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../shared/gr-formatted-text/gr-formatted-text';
-import '../../../styles/shared-styles';
import '../gr-message-scores/gr-message-scores';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-message_html';
+import {css, html, LitElement, nothing, PropertyValues} from 'lit';
import {MessageTag, SpecialFilePath} from '../../../constants/constants';
-import {customElement, property, computed, observe} from '@polymer/decorators';
+import {customElement, property, state} from 'lit/decorators';
import {
ChangeInfo,
ServerInfo,
@@ -42,6 +40,7 @@ import {
import {
ChangeMessage,
CommentThread,
+ isFormattedReviewerUpdate,
LabelExtreme,
PATCH_SET_PREFIX_PATTERN,
} from '../../../utils/comment-util';
@@ -54,6 +53,9 @@ import {
computePredecessor,
} from '../../../utils/patch-set-util';
import {isServiceUser, replaceTemplates} from '../../../utils/account-util';
+import {assertIsDefined} from '../../../utils/common-util';
+import {when} from 'lit/directives/when';
+import {FormattedReviewerUpdateInfo} from '../../../types/types';
const UPLOADED_NEW_PATCHSET_PATTERN = /Uploaded patch set (\d+)./;
const MERGED_PATCHSET_PATTERN = /(\d+) is the latest approved patch-set/;
@@ -68,11 +70,7 @@ export interface MessageAnchorTapDetail {
}
@customElement('gr-message')
-export class GrMessage extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrMessage extends LitElement {
/**
* Fired when this message's reply link is tapped.
*
@@ -98,12 +96,11 @@ export class GrMessage extends PolymerElement {
changeNum?: NumericChangeId;
@property({type: Object})
- message: ChangeMessage | undefined;
+ message?: ChangeMessage | (ChangeMessage & FormattedReviewerUpdateInfo);
@property({type: Array})
commentThreads: CommentThread[] = [];
- @computed('message')
get author() {
return this.message?.author || this.message?.updated_by;
}
@@ -114,31 +111,8 @@ export class GrMessage extends PolymerElement {
@property({type: Boolean})
hideAutomated = false;
- @property({
- type: Boolean,
- reflectToAttribute: true,
- computed: '_computeIsHidden(hideAutomated, isAutomated)',
- })
- override hidden = false;
-
- @computed('message')
- get isAutomated() {
- return !!this.message && this._computeIsAutomated(this.message);
- }
-
- @computed('message')
- get showOnBehalfOf() {
- return !!this.message && this._computeShowOnBehalfOf(this.message);
- }
-
- @property({
- type: Boolean,
- computed: '_computeShowReplyButton(message, _loggedIn)',
- })
- showReplyButton = false;
-
@property({type: String})
- projectName?: string;
+ projectName?: RepoName;
/**
* A mapping from label names to objects representing the minimum and
@@ -147,51 +121,23 @@ export class GrMessage extends PolymerElement {
@property({type: Object})
labelExtremes?: LabelExtreme;
- @property({type: Object})
- _projectConfig?: ConfigInfo;
+ @state()
+ private projectConfig?: ConfigInfo;
@property({type: Boolean})
- _loggedIn = false;
+ loggedIn = false;
- @property({type: Boolean})
- _isAdmin = false;
+ @state()
+ private isAdmin = false;
- @property({type: Boolean})
- _isDeletingChangeMsg = false;
-
- @property({type: Boolean, computed: '_computeExpanded(message.expanded)'})
- _expanded = false;
-
- @property({
- type: String,
- computed:
- '_computeMessageContentExpanded(_expanded, message.message,' +
- ' message.accounts_in_message,' +
- ' message.tag)',
- })
- _messageContentExpanded = '';
-
- @property({
- type: String,
- computed:
- '_computeMessageContentCollapsed(message.message,' +
- ' message.accounts_in_message,' +
- ' message.tag,' +
- ' commentThreads)',
- })
- _messageContentCollapsed = '';
-
- @property({
- type: String,
- computed: '_computeCommentCountText(commentThreads)',
- })
- _commentCountText = '';
+ @state()
+ private isDeletingChangeMsg = false;
private readonly restApiService = getAppContext().restApiService;
constructor() {
super();
- this.addEventListener('click', e => this._handleClick(e));
+ this.addEventListener('click', e => this.handleClick(e));
}
override connectedCallback() {
@@ -200,44 +146,380 @@ export class GrMessage extends PolymerElement {
this.config = config;
});
this.restApiService.getLoggedIn().then(loggedIn => {
- this._loggedIn = loggedIn;
+ this.loggedIn = loggedIn;
});
this.restApiService.getIsAdmin().then(isAdmin => {
- this._isAdmin = !!isAdmin;
+ this.isAdmin = !!isAdmin;
});
}
- @observe('message.expanded')
- _updateExpandedClass(expanded: boolean) {
- if (expanded) {
- this.classList.add('expanded');
- } else {
- this.classList.remove('expanded');
- }
+ static override get styles() {
+ return css`
+ :host {
+ display: block;
+ position: relative;
+ cursor: pointer;
+ overflow-y: hidden;
+ }
+ :host(.expanded) {
+ cursor: auto;
+ }
+ .collapsed .contentContainer {
+ align-items: center;
+ color: var(--deemphasized-text-color);
+ display: flex;
+ white-space: nowrap;
+ }
+ .contentContainer {
+ padding: var(--spacing-m) var(--spacing-l);
+ }
+ .expanded .contentContainer {
+ background-color: var(--background-color-secondary);
+ }
+ .collapsed .contentContainer {
+ background-color: var(--background-color-primary);
+ }
+ div.serviceUser.expanded div.contentContainer {
+ background-color: var(
+ --background-color-service-user,
+ var(--background-color-secondary)
+ );
+ }
+ div.serviceUser.collapsed div.contentContainer {
+ background-color: var(
+ --background-color-service-user,
+ var(--background-color-primary)
+ );
+ }
+ .name {
+ font-weight: var(--font-weight-bold);
+ }
+ .message {
+ --gr-formatted-text-prose-max-width: 120ch;
+ }
+ .collapsed .message {
+ max-width: none;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ .collapsed .author,
+ .collapsed .content,
+ .collapsed .message,
+ .collapsed .updateCategory,
+ gr-account-chip {
+ display: inline;
+ }
+ gr-button {
+ margin: 0 -4px;
+ }
+ .collapsed gr-thread-list,
+ .collapsed .replyBtn,
+ .collapsed .deleteBtn,
+ .collapsed .hideOnCollapsed,
+ .hideOnOpen {
+ display: none;
+ }
+ .replyBtn {
+ margin-right: var(--spacing-m);
+ }
+ .collapsed .hideOnOpen {
+ display: block;
+ }
+ .collapsed .content {
+ flex: 1;
+ margin-right: var(--spacing-m);
+ min-width: 0;
+ overflow: hidden;
+ }
+ .collapsed .content.messageContent {
+ text-overflow: ellipsis;
+ }
+ .collapsed .dateContainer {
+ position: static;
+ }
+ .collapsed .author {
+ overflow: hidden;
+ color: var(--primary-text-color);
+ margin-right: var(--spacing-s);
+ }
+ .authorLabel {
+ min-width: 130px;
+ --account-max-length: 120px;
+ margin-right: var(--spacing-s);
+ }
+ .expanded .author {
+ cursor: pointer;
+ margin-bottom: var(--spacing-m);
+ }
+ .expanded .content {
+ padding-left: 40px;
+ }
+ .dateContainer {
+ position: absolute;
+ /* right and top values should match .contentContainer padding */
+ right: var(--spacing-l);
+ top: var(--spacing-m);
+ }
+ .dateContainer gr-button {
+ margin-right: var(--spacing-m);
+ color: var(--deemphasized-text-color);
+ }
+ .dateContainer .patchset:before {
+ content: 'Patchset ';
+ }
+ .dateContainer .patchsetDiffButton {
+ margin-right: var(--spacing-m);
+ --gr-button-padding: 0 var(--spacing-m);
+ }
+ span.date {
+ color: var(--deemphasized-text-color);
+ }
+ span.date:hover {
+ text-decoration: underline;
+ }
+ .dateContainer iron-icon {
+ cursor: pointer;
+ vertical-align: top;
+ }
+ .commentsSummary {
+ margin-right: var(--spacing-s);
+ min-width: 115px;
+ }
+ .expanded .commentsSummary {
+ display: none;
+ }
+ .commentsIcon {
+ vertical-align: top;
+ }
+ gr-account-label::part(gr-account-label-text) {
+ font-weight: var(--font-weight-bold);
+ }
+ iron-icon {
+ --iron-icon-height: 20px;
+ --iron-icon-width: 20px;
+ }
+ @media screen and (max-width: 50em) {
+ .expanded .content {
+ padding-left: 0;
+ }
+ .commentsSummary {
+ min-width: 0px;
+ }
+ .authorLabel {
+ width: 100px;
+ }
+ .dateContainer .patchset:before {
+ content: 'PS ';
+ }
+ }
+ `;
}
- _computeCommentCountText(commentThreads?: CommentThread[]) {
- if (!commentThreads?.length) {
- return undefined;
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('projectName')) {
+ this.projectNameChanged();
}
-
- return pluralize(commentThreads.length, 'comment');
}
- _computeMessageContentExpanded(
- expanded: boolean,
- content?: string,
- accountsInMessage?: AccountInfo[],
- tag?: ReviewInputTag
- ) {
- if (!expanded) return '';
- return this._computeMessageContent(true, content, accountsInMessage, tag);
+ override render() {
+ if (!this.message) return nothing;
+ if (this.hideAutomated && this.computeIsAutomated()) return nothing;
+ this.updateExpandedClass();
+ return html` <div class=${this.computeClass()}>
+ <div class="contentContainer">
+ ${this.renderAuthor()} ${this.renderCommentsSummary()}
+ ${this.renderMessageContent()} ${this.renderReviewerUpdate()}
+ ${this.renderDateContainer()}
+ </div>
+ </div>`;
+ }
+
+ private renderAuthor() {
+ assertIsDefined(this.message, 'message');
+ return html` <div class="author" @click=${this.handleAuthorClick}>
+ ${when(
+ this.computeShowOnBehalfOf(),
+ () => html`
+ <span>
+ <span class="name">${this.message?.real_author?.name}</span>
+ on behalf of
+ </span>
+ `
+ )}
+ <gr-account-label
+ .account=${this.author}
+ class="authorLabel"
+ ></gr-account-label>
+ <gr-message-scores
+ .labelExtremes=${this.labelExtremes}
+ .message=${this.message}
+ .change=${this.change}
+ ></gr-message-scores>
+ </div>`;
+ }
+
+ private renderCommentsSummary() {
+ if (!this.commentThreads?.length) return nothing;
+
+ const commentCountText = pluralize(this.commentThreads.length, 'comment');
+ return html`
+ <div class="commentsSummary">
+ <iron-icon icon="gr-icons:comment" class="commentsIcon"></iron-icon>
+ <span class="numberOfComments">${commentCountText}</span>
+ </div>
+ `;
+ }
+
+ private renderMessageContent() {
+ if (!this.message?.message) return nothing;
+ const messageContentCollapsed =
+ this.computeMessageContent(
+ false,
+ this.message.message.substring(0, 1000),
+ this.message.accounts_in_message,
+ this.message.tag
+ ) || this.patchsetCommentSummary();
+ return html` <div class="content messageContent">
+ <div class="message hideOnOpen">${messageContentCollapsed}</div>
+ ${this.renderExpandedMessageContent()}
+ </div>`;
+ }
+
+ private renderExpandedMessageContent() {
+ if (!this.message?.expanded) return nothing;
+ const messageContentExpanded = this.computeMessageContent(
+ true,
+ this.message.message,
+ this.message.accounts_in_message,
+ this.message.tag
+ );
+ return html`
+ <gr-formatted-text
+ noTrailingMargin
+ class="message hideOnCollapsed"
+ .content=${messageContentExpanded}
+ .config=${this.projectConfig?.commentlinks}
+ ></gr-formatted-text>
+ ${when(messageContentExpanded, () => this.renderActionContainer())}
+ <gr-thread-list
+ ?hidden=${!this.commentThreads.length}
+ .threads=${this.commentThreads}
+ hide-dropdown
+ show-comment-context
+ .messageId=${this.message.id}
+ >
+ </gr-thread-list>
+ `;
+ }
+
+ private renderActionContainer() {
+ if (!this.computeShowReplyButton()) return nothing;
+ return html` <div class="replyActionContainer">
+ <gr-button class="replyBtn" link="" @click=${this.handleReplyTap}>
+ Reply
+ </gr-button>
+ ${when(
+ this.isAdmin,
+ () => html`
+ <gr-button
+ ?disabled=${this.isDeletingChangeMsg}
+ class="deleteBtn"
+ link=""
+ @click=${this.handleDeleteMessage}
+ >
+ Delete
+ </gr-button>
+ `
+ )}
+ </div>`;
+ }
+
+ private renderReviewerUpdate() {
+ assertIsDefined(this.message, 'message');
+ if (!isFormattedReviewerUpdate(this.message)) return;
+ return html` <div class="content">
+ ${this.message.updates.map(update => this.renderMessageUpdate(update))}
+ </div>`;
+ }
+
+ private renderMessageUpdate(update: {
+ message: string;
+ reviewers: AccountInfo[];
+ }) {
+ return html`<div class="updateCategory">
+ ${update.message}
+ ${update.reviewers.map(
+ reviewer => html`
+ <gr-account-chip .account=${reviewer} .change=${this.change}>
+ </gr-account-chip>
+ `
+ )}
+ </div>`;
+ }
+
+ private renderDateContainer() {
+ return html`<span class="dateContainer">
+ ${this.renderDiffButton()}
+ ${when(
+ this.message?._revision_number,
+ () => html`
+ <span class="patchset">${this.message?._revision_number} |</span>
+ `
+ )}
+ ${when(
+ this.message?.id,
+ () => html`
+ <span class="date" @click=${this.handleAnchorClick}>
+ <gr-date-formatter
+ withTooltip
+ showDateAndTime
+ .dateStr=${this.message?.date}
+ ></gr-date-formatter>
+ </span>
+ `,
+ () => html`
+ <span class="date">
+ <gr-date-formatter
+ withTooltip
+ showDateAndTime
+ .dateStr=${this.message?.date}
+ ></gr-date-formatter>
+ </span>
+ `
+ )}
+ <iron-icon
+ id="expandToggle"
+ @click=${this.toggleExpanded}
+ title="Toggle expanded state"
+ icon=${this.computeExpandToggleIcon()}
+ ></iron-icon>
+ </span>`;
+ }
+
+ private renderDiffButton() {
+ if (!this.showViewDiffButton()) return nothing;
+ return html` <gr-button
+ class="patchsetDiffButton"
+ @click=${this.handleViewPatchsetDiff}
+ link
+ >
+ View Diff
+ </gr-button>`;
+ }
+
+ private updateExpandedClass() {
+ if (this.message?.expanded) {
+ this.classList.add('expanded');
+ } else {
+ this.classList.remove('expanded');
+ }
}
- _patchsetCommentSummary(commentThreads: CommentThread[] = []) {
+ // Private but used in tests.
+ patchsetCommentSummary() {
const id = this.message?.id;
if (!id) return '';
- const patchsetThreads = commentThreads.filter(
+ const patchsetThreads = (this.commentThreads ?? []).filter(
thread => thread.path === SpecialFilePath.PATCHSET_LEVEL_COMMENTS
);
for (const thread of patchsetThreads) {
@@ -258,45 +540,29 @@ export class GrMessage extends PolymerElement {
return '';
}
- _computeMessageContentCollapsed(
- content?: string,
- accountsInMessage?: AccountInfo[],
- tag?: ReviewInputTag,
- commentThreads?: CommentThread[]
- ) {
- // Content is under text-overflow, so it's always shorten
- const shortenedContent = content?.substring(0, 1000);
- const summary = this._computeMessageContent(
- false,
- shortenedContent,
- accountsInMessage,
- tag
- );
- if (summary || !commentThreads) return summary;
- return this._patchsetCommentSummary(commentThreads);
- }
-
- _showViewDiffButton(message?: ChangeMessage) {
+ private showViewDiffButton() {
return (
- this._isNewPatchsetTag(message?.tag) || this._isMergePatchset(message)
+ this.isNewPatchsetTag(this.message?.tag) ||
+ this.isMergePatchset(this.message)
);
}
- _isMergePatchset(message?: ChangeMessage) {
+ private isMergePatchset(message?: ChangeMessage) {
return (
message?.tag === MessageTag.TAG_MERGED &&
message?.message.match(MERGED_PATCHSET_PATTERN)
);
}
- _isNewPatchsetTag(tag?: ReviewInputTag) {
+ private isNewPatchsetTag(tag?: ReviewInputTag) {
return (
tag === MessageTag.TAG_NEW_PATCHSET ||
tag === MessageTag.TAG_NEW_WIP_PATCHSET
);
}
- _handleViewPatchsetDiff(e: Event) {
+ // Private but used in tests
+ handleViewPatchsetDiff(e: Event) {
if (!this.message || !this.change) return;
let patchNum: PatchSetNum;
let basePatchNum: PatchSetNum;
@@ -323,14 +589,15 @@ export class GrMessage extends PolymerElement {
e.stopPropagation();
}
- _computeMessageContent(
+ // private but used in tests
+ computeMessageContent(
isExpanded: boolean,
content?: string,
accountsInMessage?: AccountInfo[],
tag?: ReviewInputTag
) {
if (!content) return '';
- const isNewPatchSet = this._isNewPatchsetTag(tag);
+ const isNewPatchSet = this.isNewPatchsetTag(tag);
if (accountsInMessage) {
content = replaceTemplates(content, accountsInMessage, this.config);
@@ -371,74 +638,68 @@ export class GrMessage extends PolymerElement {
return mappedLines.join('\n').trim();
}
- _computeAuthor(message: ChangeMessage) {
- return message.author || message.updated_by;
- }
-
- _computeShowOnBehalfOf(message: ChangeMessage) {
- const author = this._computeAuthor(message);
+ // private but used in tests
+ computeShowOnBehalfOf() {
+ if (!this.message) return false;
return !!(
- author &&
- message.real_author &&
- author._account_id !== message.real_author._account_id
+ this.author &&
+ this.message.real_author &&
+ this.author._account_id !== this.message.real_author._account_id
);
}
- _computeShowReplyButton(message?: ChangeMessage, loggedIn?: boolean) {
+ // private but used in tests.
+ computeShowReplyButton() {
return (
- message &&
- !!message.message &&
- loggedIn &&
- !this._computeIsAutomated(message)
+ !!this.message &&
+ !!this.message.message &&
+ this.loggedIn &&
+ !this.computeIsAutomated()
);
}
- _computeExpanded(expanded: boolean) {
- return expanded;
- }
-
- _handleClick(e: Event) {
- if (this.message?.expanded) {
+ private handleClick(e: Event) {
+ if (!this.message || this.message?.expanded) {
return;
}
e.stopPropagation();
- this.set('message.expanded', true);
+ this.message.expanded = true;
+ this.requestUpdate();
}
- _handleAuthorClick(e: Event) {
- if (!this.message?.expanded) {
+ private handleAuthorClick(e: Event) {
+ if (!this.message || !this.message?.expanded) {
return;
}
e.stopPropagation();
- this.set('message.expanded', false);
+ this.message.expanded = false;
+ this.requestUpdate();
}
- _computeIsAutomated(message: ChangeMessage) {
+ // private but used in tests.
+ computeIsAutomated() {
return !!(
- message.reviewer ||
- this._computeIsReviewerUpdate(message) ||
- (message.tag && message.tag.startsWith('autogenerated'))
+ this.message?.reviewer ||
+ this.computeIsReviewerUpdate() ||
+ (this.message?.tag && this.message.tag.startsWith('autogenerated'))
);
}
- _computeIsHidden(hideAutomated: boolean, isAutomated: boolean) {
- return hideAutomated && isAutomated;
- }
-
- _computeIsReviewerUpdate(message: ChangeMessage) {
- return message.type === 'REVIEWER_UPDATE';
+ private computeIsReviewerUpdate() {
+ return this.message?.type === 'REVIEWER_UPDATE';
}
- _computeClass(expanded?: boolean, author?: AccountInfo) {
+ private computeClass() {
+ const expanded = this.message?.expanded;
const classes = [];
classes.push(expanded ? 'expanded' : 'collapsed');
- if (isServiceUser(author)) classes.push('serviceUser');
+ if (isServiceUser(this.author)) classes.push('serviceUser');
return classes.join(' ');
}
- _handleAnchorClick(e: Event) {
+ private handleAnchorClick(e: Event) {
e.preventDefault();
- // The element which triggers _handleAnchorClick is rendered only if
+ // The element which triggers handleAnchorClick is rendered only if
// message.id defined: the element is wrapped in dom-if if="[[message.id]]"
const detail: MessageAnchorTapDetail = {
id: this.message!.id,
@@ -452,7 +713,7 @@ export class GrMessage extends PolymerElement {
);
}
- _handleReplyTap(e: Event) {
+ private handleReplyTap(e: Event) {
e.preventDefault();
this.dispatchEvent(
new CustomEvent('reply', {
@@ -463,14 +724,14 @@ export class GrMessage extends PolymerElement {
);
}
- _handleDeleteMessage(e: Event) {
+ private handleDeleteMessage(e: Event) {
e.preventDefault();
if (!this.message || !this.message.id || !this.changeNum) return;
- this._isDeletingChangeMsg = true;
+ this.isDeletingChangeMsg = true;
this.restApiService
.deleteChangeCommitMessage(this.changeNum, this.message.id)
.then(() => {
- this._isDeletingChangeMsg = false;
+ this.isDeletingChangeMsg = false;
this.dispatchEvent(
new CustomEvent('change-message-deleted', {
detail: {message: this.message},
@@ -481,23 +742,25 @@ export class GrMessage extends PolymerElement {
});
}
- @observe('projectName')
- _projectNameChanged(name?: string) {
- if (!name) {
- this._projectConfig = undefined;
+ private projectNameChanged() {
+ if (!this.projectName) {
+ this.projectConfig = undefined;
return;
}
- this.restApiService.getProjectConfig(name as RepoName).then(config => {
- this._projectConfig = config;
+ this.restApiService.getProjectConfig(this.projectName).then(config => {
+ this.projectConfig = config;
});
}
- _computeExpandToggleIcon(expanded: boolean) {
- return expanded ? 'gr-icons:expand-less' : 'gr-icons:expand-more';
+ private computeExpandToggleIcon() {
+ return this.message?.expanded
+ ? 'gr-icons:expand-less'
+ : 'gr-icons:expand-more';
}
- _toggleExpanded(e: Event) {
+ private toggleExpanded(e: Event) {
e.stopPropagation();
- this.set('message.expanded', !this.message?.expanded);
+ if (!this.message) return;
+ this.message = {...this.message, expanded: !this.message.expanded};
}
}
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
deleted file mode 100644
index 70e6381829..0000000000
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
+++ /dev/null
@@ -1,307 +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 {
- display: block;
- position: relative;
- cursor: pointer;
- overflow-y: hidden;
- }
- :host(.expanded) {
- cursor: auto;
- }
- .collapsed .contentContainer {
- align-items: center;
- color: var(--deemphasized-text-color);
- display: flex;
- white-space: nowrap;
- }
- .contentContainer {
- padding: var(--spacing-m) var(--spacing-l);
- }
- .expanded .contentContainer {
- background-color: var(--background-color-secondary);
- }
- .collapsed .contentContainer {
- background-color: var(--background-color-primary);
- }
- div.serviceUser.expanded div.contentContainer {
- background-color: var(
- --background-color-service-user,
- var(--background-color-secondary)
- );
- }
- div.serviceUser.collapsed div.contentContainer {
- background-color: var(
- --background-color-service-user,
- var(--background-color-primary)
- );
- }
- .name {
- font-weight: var(--font-weight-bold);
- }
- .message {
- --gr-formatted-text-prose-max-width: 120ch;
- }
- .collapsed .message {
- max-width: none;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .collapsed .author,
- .collapsed .content,
- .collapsed .message,
- .collapsed .updateCategory,
- gr-account-chip {
- display: inline;
- }
- gr-button {
- margin: 0 -4px;
- }
- .collapsed gr-thread-list,
- .collapsed .replyBtn,
- .collapsed .deleteBtn,
- .collapsed .hideOnCollapsed,
- .hideOnOpen {
- display: none;
- }
- .replyBtn {
- margin-right: var(--spacing-m);
- }
- .collapsed .hideOnOpen {
- display: block;
- }
- .collapsed .content {
- flex: 1;
- margin-right: var(--spacing-m);
- min-width: 0;
- overflow: hidden;
- }
- .collapsed .content.messageContent {
- text-overflow: ellipsis;
- }
- .collapsed .dateContainer {
- position: static;
- }
- .collapsed .author {
- overflow: hidden;
- color: var(--primary-text-color);
- margin-right: var(--spacing-s);
- }
- .authorLabel {
- min-width: 130px;
- --account-max-length: 120px;
- margin-right: var(--spacing-s);
- }
- .expanded .author {
- cursor: pointer;
- margin-bottom: var(--spacing-m);
- }
- .expanded .content {
- padding-left: 40px;
- }
- .dateContainer {
- position: absolute;
- /* right and top values should match .contentContainer padding */
- right: var(--spacing-l);
- top: var(--spacing-m);
- }
- .dateContainer gr-button {
- margin-right: var(--spacing-m);
- color: var(--deemphasized-text-color);
- }
- .dateContainer .patchset:before {
- content: 'Patchset ';
- }
- .dateContainer .patchsetDiffButton {
- margin-right: var(--spacing-m);
- --gr-button-padding: 0 var(--spacing-m);
- }
- span.date {
- color: var(--deemphasized-text-color);
- }
- span.date:hover {
- text-decoration: underline;
- }
- .dateContainer iron-icon {
- cursor: pointer;
- vertical-align: top;
- }
- .commentsSummary {
- margin-right: var(--spacing-s);
- min-width: 115px;
- }
- .expanded .commentsSummary {
- display: none;
- }
- .commentsIcon {
- vertical-align: top;
- }
- gr-account-label::part(gr-account-label-text) {
- font-weight: var(--font-weight-bold);
- }
- iron-icon {
- --iron-icon-height: 20px;
- --iron-icon-width: 20px;
- }
- @media screen and (max-width: 50em) {
- .expanded .content {
- padding-left: 0;
- }
- .commentsSummary {
- min-width: 0px;
- }
- .authorLabel {
- width: 100px;
- }
- .dateContainer .patchset:before {
- content: 'PS ';
- }
- }
- </style>
- <div class$="[[_computeClass(_expanded, author)]]">
- <div class="contentContainer">
- <div class="author" on-click="_handleAuthorClick">
- <span hidden$="[[!showOnBehalfOf]]">
- <span class="name">[[message.real_author.name]]</span>
- on behalf of
- </span>
- <gr-account-label
- account="[[author]]"
- class="authorLabel"
- ></gr-account-label>
- <gr-message-scores
- label-extremes="[[labelExtremes]]"
- message="[[message]]"
- change="[[change]]"
- ></gr-message-scores>
- </div>
- <template is="dom-if" if="[[_commentCountText]]">
- <div class="commentsSummary">
- <iron-icon icon="gr-icons:comment" class="commentsIcon"></iron-icon>
- <span class="numberOfComments">[[_commentCountText]]</span>
- </div>
- </template>
- <template is="dom-if" if="[[message.message]]">
- <div class="content messageContent">
- <div class="message hideOnOpen">[[_messageContentCollapsed]]</div>
- <template is="dom-if" if="[[_expanded]]">
- <gr-formatted-text
- noTrailingMargin
- class="message hideOnCollapsed"
- content="[[_messageContentExpanded]]"
- config="[[_projectConfig.commentlinks]]"
- ></gr-formatted-text>
- <template is="dom-if" if="[[_messageContentExpanded]]">
- <div
- class="replyActionContainer"
- hidden$="[[!showReplyButton]]"
- hidden=""
- >
- <gr-button
- class="replyBtn"
- link=""
- small=""
- on-click="_handleReplyTap"
- >
- Reply
- </gr-button>
- <gr-button
- disabled$="[[_isDeletingChangeMsg]]"
- class="deleteBtn"
- hidden$="[[!_isAdmin]]"
- hidden=""
- link=""
- small=""
- on-click="_handleDeleteMessage"
- >
- Delete
- </gr-button>
- </div>
- </template>
- <gr-thread-list
- hidden$="[[!commentThreads.length]]"
- threads="[[commentThreads]]"
- hide-dropdown
- show-comment-context
- message-id="[[message.id]]"
- >
- </gr-thread-list>
- </template>
- </div>
- </template>
- <template is="dom-if" if="[[_computeIsReviewerUpdate(message)]]">
- <div class="content">
- <template is="dom-repeat" items="[[message.updates]]" as="update">
- <div class="updateCategory">
- [[update.message]]
- <template
- is="dom-repeat"
- items="[[update.reviewers]]"
- as="reviewer"
- >
- <gr-account-chip account="[[reviewer]]" change="[[change]]">
- </gr-account-chip>
- </template>
- </div>
- </template>
- </div>
- </template>
- <span class="dateContainer">
- <template is="dom-if" if="[[_showViewDiffButton(message)]]">
- <gr-button
- class="patchsetDiffButton"
- on-click="_handleViewPatchsetDiff"
- link
- >
- View Diff
- </gr-button>
- </template>
- <template is="dom-if" if="[[message._revision_number]]">
- <span class="patchset">[[message._revision_number]] |</span>
- </template>
- <template is="dom-if" if="[[!message.id]]">
- <span class="date">
- <gr-date-formatter
- withTooltip
- showDateAndTime
- date-str="[[message.date]]"
- ></gr-date-formatter>
- </span>
- </template>
- <template is="dom-if" if="[[message.id]]">
- <span class="date" on-click="_handleAnchorClick">
- <gr-date-formatter
- withTooltip
- showDateAndTime
- date-str="[[message.date]]"
- ></gr-date-formatter>
- </span>
- </template>
- <iron-icon
- id="expandToggle"
- on-click="_toggleExpanded"
- title="Toggle expanded state"
- icon="[[_computeExpandToggleIcon(_expanded)]]"
- ></iron-icon>
- </span>
- </div>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
index ffe59f0fc5..bbe39ff07f 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
@@ -51,8 +51,8 @@ import {
import {GrButton} from '../../shared/gr-button/gr-button';
import {CommentSide} from '../../../constants/constants';
import {SinonStubbedMember} from 'sinon';
-
-const basicFixture = fixtureFromElement('gr-message');
+import {html} from 'lit';
+import {fixture} from '@open-wc/testing-helpers';
suite('gr-message tests', () => {
let element: GrMessage;
@@ -60,8 +60,7 @@ suite('gr-message tests', () => {
suite('when admin and logged in', () => {
setup(async () => {
stubRestApi('getIsAdmin').returns(Promise.resolve(true));
- element = basicFixture.instantiate();
- await flush();
+ element = await fixture<GrMessage>(html`<gr-message></gr-message>`);
});
test('reply event', async () => {
@@ -85,9 +84,7 @@ suite('gr-message tests', () => {
promise.resolve();
});
await flush();
- assert.isFalse(
- queryAndAssert<HTMLElement>(element, '.replyActionContainer').hidden
- );
+ assert.isOk(query<HTMLElement>(element, '.replyActionContainer'));
tap(queryAndAssert(element, '.replyBtn'));
await promise;
});
@@ -106,9 +103,9 @@ suite('gr-message tests', () => {
_revision_number: 1 as PatchSetNum,
expanded: true,
};
+ await element.updateComplete;
- await flush();
- assert.isFalse(queryAndAssert<HTMLElement>(element, '.deleteBtn').hidden);
+ assert.isOk(query<HTMLElement>(element, '.deleteBtn'));
});
test('delete change message', async () => {
@@ -126,11 +123,13 @@ suite('gr-message tests', () => {
_revision_number: 1 as PatchSetNum,
expanded: true,
};
+ await element.updateComplete;
const promise = mockPromise();
element.addEventListener(
'change-message-deleted',
- (e: CustomEvent<ChangeMessageDeletedEventDetail>) => {
+ async (e: CustomEvent<ChangeMessageDeletedEventDetail>) => {
+ await element.updateComplete;
assert.deepEqual(e.detail.message, element.message);
assert.isFalse(
queryAndAssert<GrButton>(element, '.deleteBtn').disabled
@@ -138,82 +137,192 @@ suite('gr-message tests', () => {
promise.resolve();
}
);
- await flush();
tap(queryAndAssert(element, '.deleteBtn'));
+ await element.updateComplete;
assert.isTrue(queryAndAssert<GrButton>(element, '.deleteBtn').disabled);
await promise;
});
- test('autogenerated prefix hiding', () => {
+ test('autogenerated prefix hiding', async () => {
element.message = {
...createChangeMessage(),
tag: 'autogenerated:gerrit:test' as ReviewInputTag,
expanded: false,
};
-
- assert.isTrue(element.isAutomated);
- assert.isFalse(element.hidden);
+ await element.updateComplete;
+
+ assert.isTrue(element.computeIsAutomated());
+ expect(element).shadowDom.to.equal(/* HTML */ `<div class="collapsed">
+ <div class="contentContainer">
+ <div class="author">
+ <gr-account-label class="authorLabel"> </gr-account-label>
+ <gr-message-scores> </gr-message-scores>
+ </div>
+ <div class="content messageContent">
+ <div class="hideOnOpen message">
+ This is a message with id cm_id_1
+ </div>
+ </div>
+ <span class="dateContainer">
+ <span class="date">
+ <gr-date-formatter showdateandtime="" withtooltip="">
+ </gr-date-formatter>
+ </span>
+ <iron-icon
+ icon="gr-icons:expand-more"
+ id="expandToggle"
+ title="Toggle expanded state"
+ >
+ </iron-icon>
+ </span>
+ </div>
+ </div>`);
element.hideAutomated = true;
+ await element.updateComplete;
- assert.isTrue(element.hidden);
+ expect(element).shadowDom.to.equal(/* HTML */ '');
});
- test('reviewer message treated as autogenerated', () => {
+ test('reviewer message treated as autogenerated', async () => {
element.message = {
...createChangeMessage(),
tag: 'autogenerated:gerrit:test' as ReviewInputTag,
reviewer: {},
expanded: false,
};
-
- assert.isTrue(element.isAutomated);
- assert.isFalse(element.hidden);
+ await element.updateComplete;
+
+ assert.isTrue(element.computeIsAutomated());
+ expect(element).shadowDom.to.equal(/* HTML */ `<div class="collapsed">
+ <div class="contentContainer">
+ <div class="author">
+ <gr-account-label class="authorLabel"> </gr-account-label>
+ <gr-message-scores> </gr-message-scores>
+ </div>
+ <div class="content messageContent">
+ <div class="hideOnOpen message">
+ This is a message with id cm_id_1
+ </div>
+ </div>
+ <span class="dateContainer">
+ <span class="date">
+ <gr-date-formatter showdateandtime="" withtooltip="">
+ </gr-date-formatter>
+ </span>
+ <iron-icon
+ icon="gr-icons:expand-more"
+ id="expandToggle"
+ title="Toggle expanded state"
+ >
+ </iron-icon>
+ </span>
+ </div>
+ </div>`);
element.hideAutomated = true;
+ await element.updateComplete;
- assert.isTrue(element.hidden);
+ expect(element).shadowDom.to.equal(/* HTML */ '');
});
- test('batch reviewer message treated as autogenerated', () => {
+ test('batch reviewer message treated as autogenerated', async () => {
element.message = {
...createChangeMessage(),
type: 'REVIEWER_UPDATE',
reviewer: {},
expanded: false,
+ updates: [],
};
-
- assert.isTrue(element.isAutomated);
- assert.isFalse(element.hidden);
+ await element.updateComplete;
+
+ assert.isTrue(element.computeIsAutomated());
+ expect(element).shadowDom.to.equal(/* HTML */ `<div class="collapsed">
+ <div class="contentContainer">
+ <div class="author">
+ <gr-account-label class="authorLabel"> </gr-account-label>
+ <gr-message-scores> </gr-message-scores>
+ </div>
+ <div class="content messageContent">
+ <div class="hideOnOpen message">
+ This is a message with id cm_id_1
+ </div>
+ </div>
+ <div class="content"></div>
+ <span class="dateContainer">
+ <span class="date">
+ <gr-date-formatter showdateandtime="" withtooltip="">
+ </gr-date-formatter>
+ </span>
+ <iron-icon
+ icon="gr-icons:expand-more"
+ id="expandToggle"
+ title="Toggle expanded state"
+ >
+ </iron-icon>
+ </span>
+ </div>
+ </div>`);
element.hideAutomated = true;
+ await element.updateComplete;
- assert.isTrue(element.hidden);
+ expect(element).shadowDom.to.equal(/* HTML */ '');
});
- test('tag that is not autogenerated prefix does not hide', () => {
+ test('tag that is not autogenerated prefix does not hide', async () => {
element.message = {
...createChangeMessage(),
tag: 'something' as ReviewInputTag,
expanded: false,
};
-
- assert.isFalse(element.isAutomated);
- assert.isFalse(element.hidden);
+ await element.updateComplete;
+
+ assert.isFalse(element.computeIsAutomated());
+ const rendered = /* HTML */ `<div class="collapsed">
+ <div class="contentContainer">
+ <div class="author">
+ <gr-account-label class="authorLabel"> </gr-account-label>
+ <gr-message-scores> </gr-message-scores>
+ </div>
+ <div class="content messageContent">
+ <div class="hideOnOpen message">
+ This is a message with id cm_id_1
+ </div>
+ </div>
+ <span class="dateContainer">
+ <span class="date">
+ <gr-date-formatter showdateandtime="" withtooltip="">
+ </gr-date-formatter>
+ </span>
+ <iron-icon
+ icon="gr-icons:expand-more"
+ id="expandToggle"
+ title="Toggle expanded state"
+ >
+ </iron-icon>
+ </span>
+ </div>
+ </div>`;
+ expect(element).shadowDom.to.equal(rendered);
element.hideAutomated = true;
+ await element.updateComplete;
+ console.error(element.computeIsAutomated());
- assert.isFalse(element.hidden);
+ expect(element).shadowDom.to.equal(rendered);
});
test('reply button hidden unless logged in', () => {
- const message = {
+ element.message = {
...createChangeMessage(),
message: 'Uploaded patch set 1.',
expanded: false,
};
- assert.isFalse(element._computeShowReplyButton(message, false));
- assert.isTrue(element._computeShowReplyButton(message, true));
+ element.loggedIn = false;
+ assert.isFalse(element.computeShowReplyButton());
+ element.loggedIn = true;
+ assert.isTrue(element.computeShowReplyButton());
});
test('_computeShowOnBehalfOf', () => {
@@ -222,29 +331,32 @@ suite('gr-message tests', () => {
message: '...',
expanded: false,
};
- assert.isNotOk(element._computeShowOnBehalfOf(message));
+ element.message = message;
+ assert.isNotOk(element.computeShowOnBehalfOf());
message.author = {_account_id: 1115495 as AccountId};
- assert.isNotOk(element._computeShowOnBehalfOf(message));
+ assert.isNotOk(element.computeShowOnBehalfOf());
message.real_author = {_account_id: 1115495 as AccountId};
- assert.isNotOk(element._computeShowOnBehalfOf(message));
+ assert.isNotOk(element.computeShowOnBehalfOf());
message.real_author._account_id = 123456 as AccountId;
- assert.isOk(element._computeShowOnBehalfOf(message));
+ assert.isOk(element.computeShowOnBehalfOf());
message.updated_by = message.author;
delete message.author;
- assert.isOk(element._computeShowOnBehalfOf(message));
+ assert.isOk(element.computeShowOnBehalfOf());
delete message.updated_by;
- assert.isNotOk(element._computeShowOnBehalfOf(message));
+ assert.isNotOk(element.computeShowOnBehalfOf());
});
- test('clicking on date link fires event', () => {
+ test('clicking on date link fires event', async () => {
element.message = {
...createChangeMessage(),
type: 'REVIEWER_UPDATE',
reviewer: {},
id: '47c43261_55aa2c41' as ChangeMessageId,
expanded: false,
+ updates: [],
};
- flush();
+ await element.updateComplete;
+
const stub = sinon.stub();
element.addEventListener('message-anchor-tap', stub);
const dateEl = queryAndAssert(element, '.date');
@@ -252,7 +364,7 @@ suite('gr-message tests', () => {
tap(dateEl);
assert.isTrue(stub.called);
- assert.deepEqual(stub.lastCall.args[0].detail, {id: element.message.id});
+ assert.deepEqual(stub.lastCall.args[0].detail, {id: element.message?.id});
});
suite('uploaded patchset X message navigates to X - 1 vs X', () => {
@@ -267,7 +379,7 @@ suite('gr-message tests', () => {
...createChangeMessage(),
message: 'Uploaded patch set 1.',
};
- element._handleViewPatchsetDiff(new MouseEvent('click'));
+ element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(
navStub.calledWithExactly(element.change!, {
patchNum: 1 as PatchSetNum,
@@ -281,7 +393,7 @@ suite('gr-message tests', () => {
...createChangeMessage(),
message: 'Uploaded patch set 2.',
};
- element._handleViewPatchsetDiff(new MouseEvent('click'));
+ element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(
navStub.calledWithExactly(element.change!, {
patchNum: 2 as PatchSetNum,
@@ -293,7 +405,7 @@ suite('gr-message tests', () => {
...createChangeMessage(),
message: 'Uploaded patch set 200.',
};
- element._handleViewPatchsetDiff(new MouseEvent('click'));
+ element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(
navStub.calledWithExactly(element.change!, {
patchNum: 200 as PatchSetNum,
@@ -307,7 +419,7 @@ suite('gr-message tests', () => {
...createChangeMessage(),
message: 'Commit message updated.',
};
- element._handleViewPatchsetDiff(new MouseEvent('click'));
+ element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(
navStub.calledWithExactly(element.change!, {
patchNum: 4 as PatchSetNum,
@@ -321,7 +433,7 @@ suite('gr-message tests', () => {
...createChangeMessage(),
message: 'abcd↵3 is the latest approved patch-set.↵abc',
};
- element._handleViewPatchsetDiff(new MouseEvent('click'));
+ element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(
navStub.calledWithExactly(element.change!, {
patchNum: 4 as PatchSetNum,
@@ -334,7 +446,7 @@ suite('gr-message tests', () => {
suite('compute messages', () => {
test('empty', () => {
assert.equal(
- element._computeMessageContent(
+ element.computeMessageContent(
true,
'',
undefined,
@@ -343,7 +455,7 @@ suite('gr-message tests', () => {
''
);
assert.equal(
- element._computeMessageContent(
+ element.computeMessageContent(
false,
'',
undefined,
@@ -356,13 +468,9 @@ suite('gr-message tests', () => {
test('new patchset', () => {
const original = 'Uploaded patch set 1.';
const tag = 'autogenerated:gerrit:newPatchSet' as ReviewInputTag;
- let actual = element._computeMessageContent(true, original, [], tag);
- assert.equal(
- actual,
- element._computeMessageContentCollapsed(original, [], tag, [])
- );
+ let actual = element.computeMessageContent(true, original, [], tag);
assert.equal(actual, original);
- actual = element._computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(false, original, [], tag);
assert.equal(actual, original);
});
@@ -370,13 +478,9 @@ suite('gr-message tests', () => {
const original = 'Patch Set 27: Patch Set 26 was rebased';
const tag = 'autogenerated:gerrit:newPatchSet' as ReviewInputTag;
const expected = 'Patch Set 26 was rebased';
- let actual = element._computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(true, original, [], tag);
assert.equal(actual, expected);
- assert.equal(
- actual,
- element._computeMessageContentCollapsed(original, [], tag, [])
- );
- actual = element._computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(false, original, [], tag);
assert.equal(actual, expected);
});
@@ -384,31 +488,27 @@ suite('gr-message tests', () => {
const original = 'Patch Set 1:\n\nThis change is ready for review.';
const tag = undefined;
const expected = 'This change is ready for review.';
- let actual = element._computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(true, original, [], tag);
assert.equal(actual, expected);
- assert.equal(
- actual,
- element._computeMessageContentCollapsed(original, [], tag, [])
- );
- actual = element._computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(false, original, [], tag);
assert.equal(actual, expected);
});
test('new patchset with vote', () => {
const original = 'Uploaded patch set 2: Code-Review+1';
const tag = 'autogenerated:gerrit:newPatchSet' as ReviewInputTag;
const expected = 'Uploaded patch set 2: Code-Review+1';
- let actual = element._computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(true, original, [], tag);
assert.equal(actual, expected);
- actual = element._computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(false, original, [], tag);
assert.equal(actual, expected);
});
test('vote', () => {
const original = 'Patch Set 1: Code-Style+1';
const tag = undefined;
const expected = '';
- let actual = element._computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(true, original, [], tag);
assert.equal(actual, expected);
- actual = element._computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(false, original, [], tag);
assert.equal(actual, expected);
});
@@ -416,9 +516,9 @@ suite('gr-message tests', () => {
const original = 'Patch Set 1:\n\n(3 comments)';
const tag = undefined;
const expected = '';
- let actual = element._computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(true, original, [], tag);
assert.equal(actual, expected);
- actual = element._computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(false, original, [], tag);
assert.equal(actual, expected);
});
@@ -432,14 +532,14 @@ suite('gr-message tests', () => {
createAccountWithIdNameAndEmail(1),
createAccountWithIdNameAndEmail(2),
];
- let actual = element._computeMessageContent(
+ let actual = element.computeMessageContent(
true,
original,
accountsInMessage,
tag
);
assert.equal(actual, expected);
- actual = element._computeMessageContent(
+ actual = element.computeMessageContent(
false,
original,
accountsInMessage,
@@ -454,9 +554,9 @@ suite('gr-message tests', () => {
const tag = undefined;
const expected =
'Removed vote: \n\n * Code-Style+1 by Gerrit Account 1\n * Code-Style-1 by Gerrit Account 2';
- let actual = element._computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(true, original, [], tag);
assert.equal(actual, expected);
- actual = element._computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(false, original, [], tag);
assert.equal(actual, expected);
});
});
@@ -466,11 +566,10 @@ suite('gr-message tests', () => {
setup(async () => {
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getIsAdmin').returns(Promise.resolve(false));
- element = basicFixture.instantiate();
- await flush();
+ element = await fixture<GrMessage>(html`<gr-message></gr-message>`);
});
- test('reply and delete button should be hidden', () => {
+ test('reply and delete button should be hidden', async () => {
element.message = {
...createChangeMessage(),
id: '47c43261_55aa2c41' as ChangeMessageId,
@@ -485,25 +584,24 @@ suite('gr-message tests', () => {
expanded: true,
};
- flush();
- assert.isTrue(
- queryAndAssert<HTMLElement>(element, '.replyActionContainer').hidden
- );
- assert.isTrue(queryAndAssert<HTMLElement>(element, '.deleteBtn').hidden);
+ await element.updateComplete;
+ assert.isNotOk(query<HTMLElement>(element, '.replyActionContainer'));
+ assert.isNotOk(query<HTMLElement>(element, '.deleteBtn'));
});
});
- suite('patchset comment summary', () => {
- setup(() => {
- element = basicFixture.instantiate();
+ suite('patchset comment summary', async () => {
+ setup(async () => {
+ element = await fixture<GrMessage>(html`<gr-message></gr-message>`);
element.message = {
...createChangeMessage(),
id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3' as ChangeMessageId,
};
+ await element.updateComplete;
});
test('single patchset comment posted', () => {
- const threads = [
+ element.commentThreads = [
{
comments: [
{
@@ -516,7 +614,6 @@ suite('gr-message tests', () => {
message: 'testing the load',
unresolved: false,
path: '/PATCHSET_LEVEL',
- collapsed: false,
},
],
patchNum: 1 as PatchSetNum,
@@ -525,23 +622,15 @@ suite('gr-message tests', () => {
commentSide: CommentSide.REVISION,
},
];
+ assert.equal(element.patchsetCommentSummary(), 'testing the load');
assert.equal(
- element._computeMessageContentCollapsed(
- '',
- undefined,
- undefined,
- threads
- ),
- 'testing the load'
- );
- assert.equal(
- element._computeMessageContent(false, '', undefined, undefined),
+ element.computeMessageContent(false, '', undefined, undefined),
''
);
});
test('single patchset comment with reply', () => {
- const threads = [
+ element.commentThreads = [
{
comments: [
{
@@ -552,7 +641,6 @@ suite('gr-message tests', () => {
message: 'testing the load',
unresolved: false,
path: '/PATCHSET_LEVEL',
- collapsed: false,
},
{
change_message_id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3',
@@ -564,7 +652,6 @@ suite('gr-message tests', () => {
unresolved: false,
path: '/PATCHSET_LEVEL',
__draft: true,
- collapsed: true,
},
],
patchNum: 1 as PatchSetNum,
@@ -573,17 +660,9 @@ suite('gr-message tests', () => {
commentSide: CommentSide.REVISION,
},
];
+ assert.equal(element.patchsetCommentSummary(), 'n');
assert.equal(
- element._computeMessageContentCollapsed(
- '',
- undefined,
- undefined,
- threads
- ),
- 'n'
- );
- assert.equal(
- element._computeMessageContent(false, '', undefined, undefined),
+ element.computeMessageContent(false, '', undefined, undefined),
''
);
});
@@ -592,11 +671,10 @@ suite('gr-message tests', () => {
suite('when logged in but not admin', () => {
setup(async () => {
stubRestApi('getIsAdmin').returns(Promise.resolve(false));
- element = basicFixture.instantiate();
- await flush();
+ element = await fixture<GrMessage>(html`<gr-message></gr-message>`);
});
- test('can see reply but not delete button', () => {
+ test('can see reply but not delete button', async () => {
element.message = {
...createChangeMessage(),
id: '47c43261_55aa2c41' as ChangeMessageId,
@@ -610,17 +688,16 @@ suite('gr-message tests', () => {
_revision_number: 1 as PatchSetNum,
expanded: true,
};
+ await element.updateComplete;
- flush();
- assert.isFalse(
- queryAndAssert<HTMLElement>(element, '.replyActionContainer').hidden
- );
- assert.isTrue(queryAndAssert<HTMLElement>(element, '.deleteBtn').hidden);
+ assert.isOk(query<HTMLElement>(element, '.replyActionContainer'));
+ assert.isNotOk(query<HTMLElement>(element, '.deleteBtn'));
});
- test('reply button shown when message is updated', () => {
+ test('reply button shown when message is updated', async () => {
element.message = undefined;
- flush();
+ await element.updateComplete;
+
let replyEl = query(element, '.replyActionContainer');
// We don't even expect the button to show up in the DOM when the message
// is undefined.
@@ -639,10 +716,10 @@ suite('gr-message tests', () => {
_revision_number: 1 as PatchSetNum,
expanded: true,
};
- flush();
+ await element.updateComplete;
+
replyEl = queryAndAssert(element, '.replyActionContainer');
assert.isOk(replyEl);
- assert.isFalse((replyEl as HTMLElement).hidden);
});
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
index 28acbea805..f6b0ad4cc3 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -57,6 +57,7 @@ import {
import {commentsModelToken} from '../../../models/comments/comments-model';
import {changeModelToken} from '../../../models/change/change-model';
import {resolve, DIPolymerElement} from '../../../models/dependency';
+import {queryAll} from '../../../utils/common-util';
/**
* The content of the enum is also used in the UI for the button text.
@@ -305,7 +306,7 @@ export class GrMessagesList extends DIPolymerElement {
super.disconnectedCallback();
}
- scrollToMessage(messageID: string) {
+ async scrollToMessage(messageID: string) {
const selector = `[data-message-id="${messageID}"]`;
const el = this.shadowRoot!.querySelector(selector) as
| GrMessage
@@ -317,13 +318,15 @@ export class GrMessagesList extends DIPolymerElement {
);
return;
}
- if (!el) {
+ if (!el || !el.message) {
this._showAllActivity = true;
setTimeout(() => this.scrollToMessage(messageID));
return;
}
- el.set('message.expanded', true);
+ el.message.expanded = true;
+ el.requestUpdate();
+ await el.updateComplete;
let top = el.offsetTop;
for (
let offsetParent = el.offsetParent as HTMLElement | null;
@@ -409,11 +412,14 @@ export class GrMessagesList extends DIPolymerElement {
}
_updateExpandedStateOfAllMessages(exp: boolean) {
- if (this._combinedMessages) {
- for (let i = 0; i < this._combinedMessages.length; i++) {
- this._combinedMessages[i].expanded = exp;
- this.notifyPath(`_combinedMessages.${i}.expanded`);
- }
+ if (!this._combinedMessages) return;
+
+ for (let i = 0; i < this._combinedMessages.length; i++) {
+ this._combinedMessages[i].expanded = exp;
+ this.notifyPath(`_combinedMessages.${i}.expanded`);
+ }
+ for (const message of queryAll<GrMessage>(this, 'gr-message')) {
+ message.requestUpdate('message');
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts
index 283ea3579d..b9cb616d29 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts
@@ -41,6 +41,7 @@ import {
UrlEncodedCommentId,
} from '../../../types/common';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
+import {assertIsDefined} from '../../../utils/common-util';
createCommentApiMockWithTemplateElement(
'gr-messages-list-comment-mock-api',
@@ -167,43 +168,53 @@ suite('gr-messages-list tests', () => {
await flush();
});
- test('expand/collapse all', () => {
+ test('expand/collapse all', async () => {
let allMessageEls = getMessages();
for (const message of allMessageEls) {
- message._expanded = false;
+ assertIsDefined(message.message);
+ message.message = {...message.message, expanded: false};
+ await message.updateComplete;
}
MockInteractions.tap(allMessageEls[1]);
- assert.isTrue(allMessageEls[1]._expanded);
+ assert.isTrue(allMessageEls[1].message?.expanded);
MockInteractions.tap(queryAndAssert(element, '#collapse-messages'));
allMessageEls = getMessages();
for (const message of allMessageEls) {
- assert.isTrue(message._expanded);
+ assert.isTrue(message.message?.expanded);
}
MockInteractions.tap(queryAndAssert(element, '#collapse-messages'));
allMessageEls = getMessages();
for (const message of allMessageEls) {
- assert.isFalse(message._expanded);
+ assert.isFalse(message.message?.expanded);
}
});
test('expand/collapse from external keypress', () => {
// Start with one expanded message. -> not all collapsed
element.scrollToMessage(messages[1].id);
- assert.isFalse([...getMessages()].filter(m => m._expanded).length === 0);
+ assert.isFalse(
+ [...getMessages()].filter(m => m.message?.expanded).length === 0
+ );
// Press 'z' -> all collapsed
element.handleExpandCollapse(false);
- assert.isTrue([...getMessages()].filter(m => m._expanded).length === 0);
+ assert.isTrue(
+ [...getMessages()].filter(m => m.message?.expanded).length === 0
+ );
// Press 'x' -> all expanded
element.handleExpandCollapse(true);
- assert.isTrue([...getMessages()].filter(m => !m._expanded).length === 0);
+ assert.isTrue(
+ [...getMessages()].filter(m => !m.message?.expanded).length === 0
+ );
// Press 'z' -> all collapsed
element.handleExpandCollapse(false);
- assert.isTrue([...getMessages()].filter(m => m._expanded).length === 0);
+ assert.isTrue(
+ [...getMessages()].filter(m => m.message?.expanded).length === 0
+ );
});
test('showAllActivity does not appear when all msgs are important', () => {
@@ -211,50 +222,52 @@ suite('gr-messages-list tests', () => {
assert.isNotOk(query(element, '.showAllActivityToggle'));
});
- test('scroll to message', () => {
+ test('scroll to message', async () => {
const allMessageEls = getMessages();
for (const message of allMessageEls) {
- message.set('message.expanded', false);
+ assertIsDefined(message.message);
+ message.message = {...message.message, expanded: false};
}
const scrollToStub = sinon.stub(window, 'scrollTo');
const highlightStub = sinon.stub(element, '_highlightEl');
- element.scrollToMessage('invalid');
+ await element.scrollToMessage('invalid');
for (const message of allMessageEls) {
+ assertIsDefined(message.message);
assert.isFalse(
- message._expanded,
+ message.message.expanded,
'expected gr-message to not be expanded'
);
}
const messageID = messages[1].id;
- element.scrollToMessage(messageID);
+ await element.scrollToMessage(messageID);
assert.isTrue(
queryAndAssert<GrMessage>(element, `[data-message-id="${messageID}"]`)
- ._expanded
+ .message?.expanded
);
assert.isTrue(scrollToStub.calledOnce);
assert.isTrue(highlightStub.calledOnce);
});
- test('scroll to message offscreen', () => {
+ test('scroll to message offscreen', async () => {
const scrollToStub = sinon.stub(window, 'scrollTo');
const highlightStub = sinon.stub(element, '_highlightEl');
element.messages = generateRandomMessages(25);
- flush();
+ await element.updateComplete;
assert.isFalse(scrollToStub.called);
assert.isFalse(highlightStub.called);
const messageID = element.messages[1].id;
- element.scrollToMessage(messageID);
+ await element.scrollToMessage(messageID);
assert.isTrue(scrollToStub.calledOnce);
assert.isTrue(highlightStub.calledOnce);
assert.isTrue(
queryAndAssert<GrMessage>(element, `[data-message-id="${messageID}"]`)
- ._expanded
+ .message?.expanded
);
});
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index 2aefa9954b..669c4916c7 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -38,6 +38,7 @@ import {CommentIdToCommentThreadMap} from '../elements/diff/gr-comment-api/gr-co
import {isMergeParent, getParentIndex} from './patch-set-util';
import {DiffInfo} from '../types/diff';
import {LineNumber} from '../api/diff';
+import {FormattedReviewerUpdateInfo} from '../types/types';
export interface DraftCommentProps {
// This must be true for all drafts. Drafts received from the backend will be
@@ -98,6 +99,12 @@ export interface ChangeMessage extends ChangeMessageInfo {
commentThreads: CommentThread[];
}
+export function isFormattedReviewerUpdate(
+ message: ChangeMessage
+): message is ChangeMessage & FormattedReviewerUpdateInfo {
+ return message.type === 'REVIEWER_UPDATE';
+}
+
export type LabelExtreme = {[labelName: string]: VotingRangeInfo};
export const PATCH_SET_PREFIX_PATTERN =