Merge changes Ie43a85d1,Idec8057a,Ie5f86a27,Iedf92e10,I0ad4960f

* changes:
  Pick change 263458 into experimental version of ChangeLog
  Pick change 259816 into experimental version of ChangeLog
  Simplify experimental gr-messages-list
  Remove the messageControlsContainer from Change Log
  Add an experimental version of the Change Log
This commit is contained in:
Ben Rohlfs
2020-04-22 13:37:49 +00:00
committed by Gerrit Code Review
7 changed files with 894 additions and 5 deletions

View File

@@ -43,6 +43,7 @@ import '../gr-file-list-header/gr-file-list-header.js';
import '../gr-file-list/gr-file-list.js';
import '../gr-included-in-dialog/gr-included-in-dialog.js';
import '../gr-messages-list/gr-messages-list.js';
import '../gr-messages-list/gr-messages-list-experimental.js';
import '../gr-related-changes-list/gr-related-changes-list.js';
import '../../diff/gr-apply-fix-dialog/gr-apply-fix-dialog.js';
import '../gr-reply-dialog/gr-reply-dialog.js';
@@ -67,6 +68,7 @@ import {RevisionInfo} from '../../shared/revision-info/revision-info.js';
import {PrimaryTabs, SecondaryTabs} from '../../../constants/constants.js';
import {NO_ROBOT_COMMENTS_THREADS_MSG} from '../../../constants/messages.js';
import {appContext} from '../../../services/app-context.js';
const CHANGE_ID_ERROR = {
MISMATCH: 'mismatch',
@@ -444,6 +446,11 @@ class GrChangeView extends mixinBehaviors( [
};
}
constructor() {
super();
this.flagsService = appContext.flagsService;
}
/** @override */
created() {
super.created();
@@ -531,8 +538,14 @@ class GrChangeView extends mixinBehaviors( [
}
}
_isChangeLogExperimentEnabled() {
return this.flagsService.isEnabled('UiFeature__cleaner_changelog');
}
get messagesList() {
return this.shadowRoot.querySelector('gr-messages-list');
const tagName = this._isChangeLogExperimentEnabled()
? 'gr-messages-list-experimental' : 'gr-messages-list';
return this.shadowRoot.querySelector(tagName);
}
get threadList() {

View File

@@ -216,7 +216,8 @@ export const htmlTemplate = html`
--paper-tab-ink: var(--link-color);
}
gr-thread-list,
gr-messages-list {
gr-messages-list,
gr-messages-list-experimental {
display: block;
}
gr-thread-list {
@@ -500,7 +501,12 @@ export const htmlTemplate = html`
</paper-tabs>
<section class="changeLog">
<template is="dom-if" if="[[_isTabActive(_constants.SecondaryTabs.CHANGE_LOG, _activeTabs)]]">
<gr-messages-list class="hideOnMobileOverlay" change-num="[[_changeNum]]" labels="[[_change.labels]]" messages="[[_change.messages]]" reviewer-updates="[[_change.reviewer_updates]]" change-comments="[[_changeComments]]" project-name="[[_change.project]]" show-reply-buttons="[[_loggedIn]]" on-message-anchor-tap="_handleMessageAnchorTap" on-reply="_handleMessageReply"></gr-messages-list>
<template is="dom-if" if="[[!_isChangeLogExperimentEnabled()]]">
<gr-messages-list class="hideOnMobileOverlay" change-num="[[_changeNum]]" labels="[[_change.labels]]" messages="[[_change.messages]]" reviewer-updates="[[_change.reviewer_updates]]" change-comments="[[_changeComments]]" project-name="[[_change.project]]" show-reply-buttons="[[_loggedIn]]" on-message-anchor-tap="_handleMessageAnchorTap" on-reply="_handleMessageReply"></gr-messages-list>
</template>
<template is="dom-if" if="[[_isChangeLogExperimentEnabled()]]">
<gr-messages-list-experimental class="hideOnMobileOverlay" change-num="[[_changeNum]]" labels="[[_change.labels]]" messages="[[_change.messages]]" reviewer-updates="[[_change.reviewer_updates]]" change-comments="[[_changeComments]]" project-name="[[_change.project]]" show-reply-buttons="[[_loggedIn]]" on-message-anchor-tap="_handleMessageAnchorTap" on-reply="_handleMessageReply"></gr-messages-list-experimental>
</template>
</template>
<template is="dom-if" if="[[_isTabActive(_constants.SecondaryTabs.COMMENT_THREADS, _activeTabs)]]">
<gr-thread-list threads="[[_commentThreads]]" change="[[_change]]" change-num="[[_changeNum]]" logged-in="[[_loggedIn]]" only-show-robot-comments-with-human-reply="" on-thread-list-modified="_handleReloadDiffComments"></gr-thread-list>

View File

@@ -287,8 +287,8 @@ class GrMessage extends GestureEventListeners(
return hideAutomated && isAutomated;
}
_computeIsReviewerUpdate(event) {
return event.type === 'REVIEWER_UPDATE';
_computeIsReviewerUpdate(message) {
return message.type === 'REVIEWER_UPDATE';
}
_getScores(message, labelExtremes) {

View File

@@ -0,0 +1,365 @@
/**
* @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 '../../../scripts/bundled-polymer.js';
import '@polymer/paper-toggle-button/paper-toggle-button.js';
import '../../core/gr-reporting/gr-reporting.js';
import '../../shared/gr-button/gr-button.js';
import '../gr-message/gr-message.js';
import '../../../styles/shared-styles.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {htmlTemplate} from './gr-messages-list-experimental_html.js';
import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
import {util} from '../../../scripts/util.js';
/**
* The content of the enum is also used in the UI for the button text.
*
* @enum {string}
*/
const ExpandAllState = {
EXPAND_ALL: 'Expand All',
COLLAPSE_ALL: 'Collapse All',
};
/**
* @extends Polymer.Element
*/
class GrMessagesListExperimental extends mixinBehaviors( [
KeyboardShortcutBehavior,
], GestureEventListeners(
LegacyElementMixin(
PolymerElement))) {
static get template() { return htmlTemplate; }
static get is() { return 'gr-messages-list-experimental'; }
static get properties() {
return {
changeNum: Number,
/**
* These are just the change messages. They are combined with reviewer
* updates below. So _combinedMessages is the more important property.
*/
messages: {
type: Array,
value() { return []; },
},
/**
* These are just the reviewer updates. They are combined with change
* messages above. So _combinedMessages is the more important property.
*/
reviewerUpdates: {
type: Array,
value() { return []; },
},
changeComments: Object,
projectName: String,
showReplyButtons: {
type: Boolean,
value: false,
},
labels: Object,
/**
* Keeps track of the state of the "Expand All" toggle button. Note that
* you can individually expand/collapse some messages without affecting
* the toggle button's state.
*
* @type {ExpandAllState}
*/
_expandAllState: {
type: String,
value: ExpandAllState.EXPAND_ALL,
},
_expandAllTitle: {
type: String,
computed: '_computeExpandAllTitle(_expandAllState)',
},
_hideAutomated: {
type: Boolean,
value: false,
observer: '_hideAutomatedChanged',
},
/**
* The merged array of change messages and reviewer updates.
*/
_combinedMessages: {
type: Array,
computed: '_computeCombinedMessages(messages, reviewerUpdates)',
observer: '_combinedMessagesChanged',
},
_labelExtremes: {
type: Object,
computed: '_computeLabelExtremes(labels.*)',
},
};
}
scrollToMessage(messageID) {
const selector = `[data-message-id="${messageID}"]`;
const el = this.shadowRoot.querySelector(selector);
if (!el && !this._hideAutomated) {
console.warn(`Failed to scroll to message: ${messageID}`);
return;
}
if (!el) {
this._hideAutomated = false;
setTimeout(() => this.scrollToMessage(messageID));
return;
}
el.set('message.expanded', true);
let top = el.offsetTop;
for (let offsetParent = el.offsetParent;
offsetParent;
offsetParent = offsetParent.offsetParent) {
top += offsetParent.offsetTop;
}
window.scrollTo(0, top);
this._highlightEl(el);
}
_isAutomated(message) {
const isReviewerUpdate =
!!(message.reviewer || message.type === 'REVIEWER_UPDATE');
const isAutoGenerated =
!!(message.tag && message.tag.startsWith('autogenerated'));
return isReviewerUpdate || isAutoGenerated;
}
_hideAutomatedChanged(hideAutomated) {
// We have to call render() such that the dom-repeat filter picks up the
// change.
this.$.messageRepeat.render();
}
/**
* Filter for the dom-repeat of combinedMessages.
*/
_isMessageVisible(message) {
return !(this._hideAutomated && this._isAutomated(message));
}
/**
* Merges change messages and reviewer updates into one array.
*/
_computeCombinedMessages(messages, reviewerUpdates) {
messages = messages || [];
reviewerUpdates = reviewerUpdates || [];
let mi = 0;
let ri = 0;
let combinedMessages = [];
let mDate;
let rDate;
for (let i = 0; i < messages.length; i++) {
messages[i]._index = i;
}
while (mi < messages.length || ri < reviewerUpdates.length) {
if (mi >= messages.length) {
combinedMessages = combinedMessages.concat(reviewerUpdates.slice(ri));
break;
}
if (ri >= reviewerUpdates.length) {
combinedMessages = combinedMessages.concat(messages.slice(mi));
break;
}
mDate = mDate || util.parseDate(messages[mi].date);
rDate = rDate || util.parseDate(reviewerUpdates[ri].date);
if (rDate < mDate) {
combinedMessages.push(reviewerUpdates[ri++]);
rDate = null;
} else {
combinedMessages.push(messages[mi++]);
mDate = null;
}
}
combinedMessages.forEach(m => {
if (m.expanded === undefined) {
m.expanded = false;
}
});
return combinedMessages;
}
_updateExpandedStateOfAllMessages(exp) {
if (this._combinedMessages) {
for (let i = 0; i < this._combinedMessages.length; i++) {
this._combinedMessages[i].expanded = exp;
this.notifyPath(`_combinedMessages.${i}.expanded`);
}
}
}
_computeExpandAllTitle(_expandAllState) {
if (_expandAllState === ExpandAllState.COLLAPSED_ALL) {
return this.createTitle(
this.Shortcut.COLLAPSE_ALL_MESSAGES, this.ShortcutSection.ACTIONS);
}
if (_expandAllState === ExpandAllState.EXPAND_ALL) {
return this.createTitle(
this.Shortcut.EXPAND_ALL_MESSAGES, this.ShortcutSection.ACTIONS);
}
return '';
}
_highlightEl(el) {
const highlightedEls =
dom(this.root).querySelectorAll('.highlighted');
for (const highlighedEl of highlightedEls) {
highlighedEl.classList.remove('highlighted');
}
function handleAnimationEnd() {
el.removeEventListener('animationend', handleAnimationEnd);
el.classList.remove('highlighted');
}
el.addEventListener('animationend', handleAnimationEnd);
el.classList.add('highlighted');
}
/**
* @param {boolean} expand
*/
handleExpandCollapse(expand) {
this._expandAllState = expand ? ExpandAllState.COLLAPSE_ALL
: ExpandAllState.EXPAND_ALL;
this._updateExpandedStateOfAllMessages(expand);
}
_handleExpandCollapseTap(e) {
e.preventDefault();
this.handleExpandCollapse(
this._expandAllState === ExpandAllState.EXPAND_ALL);
}
_handleAnchorClick(e) {
this.scrollToMessage(e.detail.id);
}
_hasAutomatedMessages(messages) {
if (!messages) { return false; }
for (const message of messages) {
if (this._isAutomated(message)) {
return true;
}
}
return false;
}
/**
* Computes message author's file comments for change's message.
* Method uses this.messages to find next message and relies on messages
* to be sorted by date field descending.
*
* @param {!Object} changeComments changeComment object, which includes
* a method to get all published comments (including robot comments),
* which returns a Hash of arrays of comments, filename as key.
* @param {!Object} message
* @return {!Object} Hash of arrays of comments, filename as key.
*/
_computeCommentsForMessage(changeComments, message) {
if ([changeComments, message].some(arg => arg === undefined)) {
return [];
}
const comments = changeComments.getAllPublishedComments();
if (message._index === undefined || !comments || !this.messages) {
return [];
}
const messages = this.messages || [];
const index = message._index;
const authorId = message.author && message.author._account_id;
const mDate = util.parseDate(message.date).getTime();
// NB: Messages array has oldest messages first.
let nextMDate;
if (index > 0) {
for (let i = index - 1; i >= 0; i--) {
if (messages[i] && messages[i].author &&
messages[i].author._account_id === authorId) {
nextMDate = util.parseDate(messages[i].date).getTime();
break;
}
}
}
const msgComments = {};
for (const file in comments) {
if (!comments.hasOwnProperty(file)) { continue; }
const fileComments = comments[file];
for (let i = 0; i < fileComments.length; i++) {
if (fileComments[i].author &&
fileComments[i].author._account_id !== authorId) {
continue;
}
const cDate = util.parseDate(fileComments[i].updated).getTime();
if (cDate <= mDate) {
if (nextMDate && cDate <= nextMDate) {
continue;
}
msgComments[file] = msgComments[file] || [];
msgComments[file].push(fileComments[i]);
}
}
}
return msgComments;
}
/**
* This method is for reporting stats only.
*/
_combinedMessagesChanged(combinedMessages) {
if (combinedMessages) {
if (combinedMessages.length === 0) return;
const tags = combinedMessages.map(
message => message.tag || message.type ||
(message.comments ? 'comments' : 'none'));
const tagsCounted = tags.reduce((acc, val) => {
acc[val] = (acc[val] || 0) + 1;
return acc;
}, {all: combinedMessages.length});
this.$.reporting.reportInteraction('messages-count', tagsCounted);
}
}
/**
* Compute a mapping from label name to objects representing the minimum and
* maximum possible values for that label.
*/
_computeLabelExtremes(labelRecord) {
const extremes = {};
const labels = labelRecord.base;
if (!labels) { return extremes; }
for (const key of Object.keys(labels)) {
if (!labels[key] || !labels[key].values) { continue; }
const values = Object.keys(labels[key].values)
.map(v => parseInt(v, 10));
values.sort((a, b) => a - b);
if (!values.length) { continue; }
extremes[key] = {min: values[0], max: values[values.length - 1]};
}
return extremes;
}
}
customElements.define(GrMessagesListExperimental.is,
GrMessagesListExperimental);

View File

@@ -0,0 +1,74 @@
/**
* @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.js';
export const htmlTemplate = html`
<style include="shared-styles">
:host {
display: flex;
justify-content: space-between;
}
.header {
align-items: center;
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
padding: var(--spacing-s) var(--spacing-l);
}
.highlighted {
animation: 3s fadeOut;
}
@keyframes fadeOut {
0% { background-color: var(--emphasis-color); }
100% { background-color: var(--view-background-color); }
}
.container {
align-items: center;
display: flex;
}
gr-message:not(:last-of-type) {
border-bottom: 1px solid var(--border-color);
}
gr-message:nth-child(2n) {
background-color: var(--background-color-secondary);
}
gr-message:nth-child(2n+1) {
background-color: var(--background-color-tertiary);
}
</style>
<div class="header">
<span id="automatedMessageToggleContainer" class="container" hidden\$="[[!_hasAutomatedMessages(messages)]]">
<paper-toggle-button id="automatedMessageToggle" checked="{{_hideAutomated}}"></paper-toggle-button>Only comments
<span class="transparent separator"></span>
</span>
<gr-button id="collapse-messages" link="" title="[[_expandAllTitle]]" on-click="_handleExpandCollapseTap">
[[_expandAllState]]
</gr-button>
</div>
<template id="messageRepeat" is="dom-repeat" items="[[_combinedMessages]]" as="message" filter="_isMessageVisible">
<gr-message change-num="[[changeNum]]"
message="[[message]]"
comments="[[_computeCommentsForMessage(changeComments, message)]]"
project-name="[[projectName]]"
show-reply-button="[[showReplyButtons]]"
on-message-anchor-tap="_handleAnchorClick"
label-extremes="[[_labelExtremes]]"
data-message-id\$="[[message.id]]"></gr-message>
</template>
<gr-reporting id="reporting" category="message-list"></gr-reporting>
`;

View File

@@ -0,0 +1,430 @@
<!DOCTYPE html>
<!--
@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.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-messages-list-experimental</title>
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
<script src="/components/wct-browser-legacy/browser.js"></script>
<dom-module id="comment-api-mock">
<template>
<gr-messages-list-experimental
id="messagesList"
change-comments="[[_changeComments]]"></gr-messages-list-experimental>
<gr-comment-api id="commentAPI"></gr-comment-api>
</template>
</dom-module>
<test-fixture id="basic">
<template>
<comment-api-mock>
<gr-messages-list-experimental></gr-messages-list-experimental>
</comment-api-mock>
</template>
</test-fixture>
<script type="module">
import '../../../test/common-test-setup.js';
import '../../diff/gr-comment-api/gr-comment-api.js';
import './gr-messages-list-experimental.js';
import '../../diff/gr-comment-api/gr-comment-api-mock_test.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
const randomMessage = function(opt_params) {
const params = opt_params || {};
const author1 = {
_account_id: 1115495,
name: 'Andrew Bonventre',
email: 'andybons@chromium.org',
};
return {
id: params.id || Math.random().toString(),
date: params.date || '2016-01-12 20:28:33.038000',
message: params.message || Math.random().toString(),
_revision_number: params._revision_number || 1,
author: params.author || author1,
};
};
const randomAutomated = function(opt_params) {
return Object.assign({tag: 'autogenerated:gerrit:replace'},
randomMessage(opt_params));
};
suite('gr-messages-list-experimental tests', () => {
let element;
let messages;
let sandbox;
let commentApiWrapper;
const getMessages = function() {
return dom(element.root).querySelectorAll('gr-message');
};
const author = {
_account_id: 42,
name: 'Marvin the Paranoid Android',
email: 'marvin@sirius.org',
};
const comments = {
file1: [
{
message: 'message text',
updated: '2016-09-27 00:18:03.000000000',
in_reply_to: '6505d749_f0bec0aa',
line: 62,
id: '6505d749_10ed44b2',
patch_set: 2,
author: {
email: 'some@email.com',
_account_id: 123,
},
},
{
message: 'message text',
updated: '2016-09-27 00:18:03.000000000',
in_reply_to: 'c5912363_6b820105',
line: 42,
id: '450a935e_0f1c05db',
patch_set: 2,
author,
},
{
message: 'message text',
updated: '2016-09-27 00:18:03.000000000',
in_reply_to: '6505d749_f0bec0aa',
line: 62,
id: '6505d749_10ed44b2',
patch_set: 2,
author,
},
],
file2: [
{
message: 'message text',
updated: '2016-09-27 00:18:03.000000000',
in_reply_to: 'c5912363_4b7d450a',
line: 132,
id: '450a935e_4f260d25',
patch_set: 2,
author,
},
],
};
suite('basic tests', () => {
setup(() => {
stub('gr-rest-api-interface', {
getConfig() { return Promise.resolve({}); },
getLoggedIn() { return Promise.resolve(false); },
getDiffComments() { return Promise.resolve(comments); },
getDiffRobotComments() { return Promise.resolve({}); },
getDiffDrafts() { return Promise.resolve({}); },
});
sandbox = sinon.sandbox.create();
messages = _.times(3, randomMessage);
// Element must be wrapped in an element with direct access to the
// comment API.
commentApiWrapper = fixture('basic');
element = commentApiWrapper.$.messagesList;
element.messages = messages;
// Stub methods on the changeComments object after changeComments has
// been initialized.
return commentApiWrapper.loadComments();
});
teardown(() => {
sandbox.restore();
});
test('expand/collapse all', () => {
let allMessageEls = getMessages();
for (const message of allMessageEls) {
message._expanded = false;
}
MockInteractions.tap(allMessageEls[1]);
assert.isTrue(allMessageEls[1]._expanded);
MockInteractions.tap(element.shadowRoot
.querySelector('#collapse-messages'));
allMessageEls = getMessages();
for (const message of allMessageEls) {
assert.isTrue(message._expanded);
}
MockInteractions.tap(element.shadowRoot
.querySelector('#collapse-messages'));
allMessageEls = getMessages();
for (const message of allMessageEls) {
assert.isFalse(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);
// Press 'z' -> all collapsed
element.handleExpandCollapse(false);
assert.isTrue([...getMessages()].filter(m => m._expanded).length == 0);
// Press 'x' -> all expanded
element.handleExpandCollapse(true);
assert.isTrue([...getMessages()].filter(m => !m._expanded).length == 0);
// Press 'z' -> all collapsed
element.handleExpandCollapse(false);
assert.isTrue([...getMessages()].filter(m => m._expanded).length == 0);
});
test('hide messages does not appear when no automated messages', () => {
assert.isOk(element.shadowRoot
.querySelector('#automatedMessageToggleContainer[hidden]'));
});
test('scroll to message', () => {
const allMessageEls = getMessages();
for (const message of allMessageEls) {
message.set('message.expanded', false);
}
const scrollToStub = sandbox.stub(window, 'scrollTo');
const highlightStub = sandbox.stub(element, '_highlightEl');
element.scrollToMessage('invalid');
for (const message of allMessageEls) {
assert.isFalse(message._expanded,
'expected gr-message to not be expanded');
}
const messageID = messages[1].id;
element.scrollToMessage(messageID);
assert.isTrue(
element.shadowRoot
.querySelector('[data-message-id="' + messageID + '"]')
._expanded);
assert.isTrue(scrollToStub.calledOnce);
assert.isTrue(highlightStub.calledOnce);
});
test('scroll to message offscreen', () => {
const scrollToStub = sandbox.stub(window, 'scrollTo');
const highlightStub = sandbox.stub(element, '_highlightEl');
element.messages = _.times(25, randomMessage);
flushAsynchronousOperations();
assert.isFalse(scrollToStub.called);
assert.isFalse(highlightStub.called);
const messageID = element.messages[1].id;
element.scrollToMessage(messageID);
assert.isTrue(scrollToStub.calledOnce);
assert.isTrue(highlightStub.calledOnce);
assert.isTrue(
element.shadowRoot
.querySelector('[data-message-id="' + messageID + '"]')
._expanded);
});
test('messages', () => {
const messages = [].concat(
randomMessage(),
{
_index: 5,
_revision_number: 4,
message: 'Uploaded patch set 4.',
date: '2016-09-28 13:36:33.000000000',
author,
id: '8c19ccc949c6d482b061be6a28e10782abf0e7af',
},
{
_index: 6,
_revision_number: 4,
message: 'Patch Set 4:\n\n(6 comments)',
date: '2016-09-28 13:36:33.000000000',
author,
id: 'e7bfdbc842f6b6d8064bc68e0f52b673f40c0ca5',
}
);
element.messages = messages;
const isAuthor = function(author, message) {
return message.author._account_id === author._account_id;
};
const isMarvin = isAuthor.bind(null, author);
flushAsynchronousOperations();
const messageElements = getMessages();
assert.equal(messageElements.length, messages.length);
assert.deepEqual(messageElements[1].message, messages[1]);
assert.deepEqual(messageElements[2].message, messages[2]);
assert.deepEqual(messageElements[1].comments.file1,
comments.file1.filter(isMarvin));
assert.deepEqual(messageElements[1].comments.file2,
comments.file2.filter(isMarvin));
assert.deepEqual(messageElements[2].comments, {});
});
test('messages without author do not throw', () => {
const messages = [{
_index: 5,
_revision_number: 4,
message: 'Uploaded patch set 4.',
date: '2016-09-28 13:36:33.000000000',
id: '8c19ccc949c6d482b061be6a28e10782abf0e7af',
}];
element.messages = messages;
flushAsynchronousOperations();
const messageEls = getMessages();
assert.equal(messageEls.length, 1);
assert.equal(messageEls[0].message.message, messages[0].message);
});
});
suite('gr-messages-list-experimental automate tests', () => {
let element;
let messages;
let sandbox;
let commentApiWrapper;
const getMessages = function() {
return dom(element.root).querySelectorAll('gr-message');
};
const getHiddenMessages = function() {
return dom(element.root).querySelectorAll('gr-message[hidden]');
};
const randomMessageReviewer = {
reviewer: {},
date: '2016-01-13 20:30:33.038000',
};
setup(() => {
stub('gr-rest-api-interface', {
getConfig() { return Promise.resolve({}); },
getLoggedIn() { return Promise.resolve(false); },
getDiffComments() { return Promise.resolve({}); },
getDiffRobotComments() { return Promise.resolve({}); },
getDiffDrafts() { return Promise.resolve({}); },
});
sandbox = sinon.sandbox.create();
messages = _.times(2, randomAutomated);
messages.push(randomMessageReviewer);
// Element must be wrapped in an element with direct access to the
// comment API.
commentApiWrapper = fixture('basic');
element = commentApiWrapper.$.messagesList;
sandbox.spy(commentApiWrapper.$.commentAPI, 'loadAll');
element.messages = messages;
// Stub methods on the changeComments object after changeComments has
// been initialized.
return commentApiWrapper.loadComments();
});
teardown(() => {
sandbox.restore();
});
test('hide autogenerated button is not hidden', () => {
assert.isNotOk(element.shadowRoot
.querySelector('#automatedMessageToggle[hidden]'));
});
test('autogenerated messages are not hidden initially', () => {
const allHiddenMessageEls = getHiddenMessages();
// There are no hidden messages.
assert.isFalse(!!allHiddenMessageEls.length);
});
test('autogenerated messages hidden after comments only toggle', () => {
let allHiddenMessageEls = getHiddenMessages();
element._hideAutomated = false;
MockInteractions.tap(element.$.automatedMessageToggle);
flushAsynchronousOperations();
const allMessageEls = getMessages();
allHiddenMessageEls = getHiddenMessages();
// Autogenerated messages are now hidden.
assert.equal(allHiddenMessageEls.length, allMessageEls.length);
});
test('autogenerated messages not hidden after comments only toggle',
() => {
let allHiddenMessageEls = getHiddenMessages();
element._hideAutomated = true;
MockInteractions.tap(element.$.automatedMessageToggle);
allHiddenMessageEls = getHiddenMessages();
// Autogenerated messages are now hidden.
assert.isFalse(!!allHiddenMessageEls.length);
});
test('_computeLabelExtremes', () => {
const computeSpy = sandbox.spy(element, '_computeLabelExtremes');
element.labels = null;
assert.isTrue(computeSpy.calledOnce);
assert.deepEqual(computeSpy.lastCall.returnValue, {});
element.labels = {};
assert.isTrue(computeSpy.calledTwice);
assert.deepEqual(computeSpy.lastCall.returnValue, {});
element.labels = {'my-label': {}};
assert.isTrue(computeSpy.calledThrice);
assert.deepEqual(computeSpy.lastCall.returnValue, {});
element.labels = {'my-label': {values: {}}};
assert.equal(computeSpy.callCount, 4);
assert.deepEqual(computeSpy.lastCall.returnValue, {});
element.labels = {'my-label': {values: {'-12': {}}}};
assert.equal(computeSpy.callCount, 5);
assert.deepEqual(computeSpy.lastCall.returnValue,
{'my-label': {min: -12, max: -12}});
element.labels = {
'my-label': {values: {'-2': {}, '-1': {}, '0': {}, '+1': {}, '+2': {}}},
};
assert.equal(computeSpy.callCount, 6);
assert.deepEqual(computeSpy.lastCall.returnValue,
{'my-label': {min: -2, max: 2}});
element.labels = {
'my-label': {values: {'-12': {}}},
'other-label': {values: {'-1': {}, ' 0': {}, '+1': {}}},
};
assert.equal(computeSpy.callCount, 7);
assert.deepEqual(computeSpy.lastCall.returnValue, {
'my-label': {min: -12, max: -12},
'other-label': {min: -1, max: 1},
});
});
});
});
</script>

View File

@@ -80,6 +80,7 @@ const elements = [
'change/gr-label-scores/gr-label-scores_test.html',
'change/gr-message/gr-message_test.html',
'change/gr-messages-list/gr-messages-list_test.html',
'change/gr-messages-list/gr-messages-list-experimental_test.html',
'change/gr-related-changes-list/gr-related-changes-list_test.html',
'change/gr-reply-dialog/gr-reply-dialog-it_test.html',
'change/gr-reply-dialog/gr-reply-dialog_test.html',