blob: 1ed2729594d52303cb6ca05b8769c91cadd4a8e3 [file] [log] [blame]
/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../../../test/common-test-setup';
import './gr-message';
import {
NavigationService,
navigationToken,
} from '../../core/gr-navigation/gr-navigation';
import {
createAccountWithIdNameAndEmail,
createChange,
createChangeMessage,
createComment,
createRevisions,
createLabelInfo,
createCommentThread,
} from '../../../test/test-data-generators';
import {
mockPromise,
query,
queryAndAssert,
stubRestApi,
} from '../../../test/test-utils';
import {GrMessage} from './gr-message';
import {
AccountId,
ChangeMessageId,
EmailAddress,
NumericChangeId,
RevisionPatchSetNum,
ReviewInputTag,
Timestamp,
UrlEncodedCommentId,
SavingState,
} from '../../../types/common';
import {ChangeMessageDeletedEventDetail} from '../../../types/events';
import {GrButton} from '../../shared/gr-button/gr-button';
import {CommentSide} from '../../../constants/constants';
import {SinonStubbedMember} from 'sinon';
import {html} from 'lit';
import {fixture, assert} from '@open-wc/testing';
import {testResolver} from '../../../test/common-test-setup';
suite('gr-message tests', () => {
let element: GrMessage;
suite('when admin and logged in', () => {
setup(async () => {
stubRestApi('getIsAdmin').returns(Promise.resolve(true));
element = await fixture<GrMessage>(html`<gr-message></gr-message>`);
});
test('can see delete button', async () => {
element.message = {
...createChangeMessage(),
id: '47c43261_55aa2c41' as ChangeMessageId,
author: {
_account_id: 1115495 as AccountId,
name: 'Andrew Bonventre',
email: 'andybons@chromium.org' as EmailAddress,
},
date: '2016-01-12 20:24:49.448000000' as Timestamp,
message: 'Uploaded patch set 1.',
_revision_number: 1 as RevisionPatchSetNum,
expanded: true,
};
await element.updateComplete;
assert.isOk(query<HTMLElement>(element, '.deleteBtn'));
});
test('delete change message', async () => {
element.changeNum = 314159 as NumericChangeId;
element.message = {
...createChangeMessage(),
id: '47c43261_55aa2c41' as ChangeMessageId,
author: {
_account_id: 1115495 as AccountId,
name: 'Andrew Bonventre',
email: 'andybons@chromium.org' as EmailAddress,
},
date: '2016-01-12 20:24:49.448000000' as Timestamp,
message: 'Uploaded patch set 1.',
_revision_number: 1 as RevisionPatchSetNum,
expanded: true,
};
await element.updateComplete;
const promise = mockPromise();
element.addEventListener(
'change-message-deleted',
async (e: CustomEvent<ChangeMessageDeletedEventDetail>) => {
await element.updateComplete;
assert.deepEqual(e.detail.message, element.message);
assert.isFalse(
queryAndAssert<GrButton>(element, '.deleteBtn').disabled
);
promise.resolve();
}
);
queryAndAssert<GrButton>(element, '.deleteBtn').click();
await element.updateComplete;
assert.isTrue(queryAndAssert<GrButton>(element, '.deleteBtn').disabled);
await promise;
});
test('autogenerated prefix hiding', async () => {
element.message = {
...createChangeMessage(),
tag: 'autogenerated:gerrit:test' as ReviewInputTag,
expanded: false,
};
await element.updateComplete;
assert.isTrue(element.computeIsAutomated());
assert.shadowDom.equal(
element,
/* 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>
<gr-icon
icon="expand_more"
id="expandToggle"
title="Toggle expanded state"
></gr-icon>
</span>
</div>
</div>`
);
element.hideAutomated = true;
await element.updateComplete;
assert.shadowDom.equal(element, /* HTML */ '');
});
test('reviewer message treated as autogenerated', async () => {
element.message = {
...createChangeMessage(),
tag: 'autogenerated:gerrit:test' as ReviewInputTag,
reviewer: {},
expanded: false,
};
await element.updateComplete;
assert.isTrue(element.computeIsAutomated());
assert.shadowDom.equal(
element,
/* 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>
<gr-icon
icon="expand_more"
id="expandToggle"
title="Toggle expanded state"
></gr-icon>
</span>
</div>
</div>`
);
element.hideAutomated = true;
await element.updateComplete;
assert.shadowDom.equal(element, /* HTML */ '');
});
test('batch reviewer message treated as autogenerated', async () => {
element.message = {
...createChangeMessage(),
type: 'REVIEWER_UPDATE',
reviewer: {},
expanded: false,
updates: [],
};
await element.updateComplete;
assert.isTrue(element.computeIsAutomated());
assert.shadowDom.equal(
element,
/* 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>
<gr-icon
icon="expand_more"
id="expandToggle"
title="Toggle expanded state"
></gr-icon>
</span>
</div>
</div>`
);
element.hideAutomated = true;
await element.updateComplete;
assert.shadowDom.equal(element, /* HTML */ '');
});
test('tag that is not autogenerated prefix does not hide', async () => {
element.message = {
...createChangeMessage(),
tag: 'something' as ReviewInputTag,
expanded: false,
};
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>
<gr-icon
icon="expand_more"
id="expandToggle"
title="Toggle expanded state"
></gr-icon>
</span>
</div>
</div>`;
assert.shadowDom.equal(element, rendered);
element.hideAutomated = true;
await element.updateComplete;
console.error(element.computeIsAutomated());
assert.shadowDom.equal(element, rendered);
});
test('renders comment message', async () => {
element.commentThreads = [
createCommentThread([
createComment({message: 'hello 1', unresolved: true}),
]),
createCommentThread([createComment({message: 'hello 2'})]),
];
element.message = {
...createChangeMessage(),
commentThreads: element.commentThreads,
};
await element.updateComplete;
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="commentsSummary">
<span class="numberOfComments" title="1 unresolved comment">
<gr-icon
class="commentsIcon unresolved"
small
filled
icon="chat_bubble"
>
</gr-icon>
1
</span>
<span class="numberOfComments" title="1 resolved comment">
<gr-icon
class="commentsIcon"
small
icon="mark_chat_read"
></gr-icon>
1
</span>
</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>
<gr-icon
icon="expand_more"
id="expandToggle"
title="Toggle expanded state"
></gr-icon>
</span>
</div>
</div>`;
assert.shadowDom.equal(element, rendered);
});
test('_computeShowOnBehalfOf', () => {
const message = {
...createChangeMessage(),
message: '...',
expanded: false,
};
element.message = message;
assert.isNotOk(element.computeShowOnBehalfOf());
message.author = {_account_id: 1115495 as AccountId};
assert.isNotOk(element.computeShowOnBehalfOf());
message.real_author = {_account_id: 1115495 as AccountId};
assert.isNotOk(element.computeShowOnBehalfOf());
message.real_author._account_id = 123456 as AccountId;
assert.isOk(element.computeShowOnBehalfOf());
message.updated_by = message.author;
delete message.author;
assert.isOk(element.computeShowOnBehalfOf());
delete message.updated_by;
assert.isNotOk(element.computeShowOnBehalfOf());
});
test('clicking on date link fires event', async () => {
element.message = {
...createChangeMessage(),
type: 'REVIEWER_UPDATE',
reviewer: {},
id: '47c43261_55aa2c41' as ChangeMessageId,
expanded: false,
updates: [],
};
await element.updateComplete;
const stub = sinon.stub();
element.addEventListener('message-anchor-tap', stub);
const dateEl = queryAndAssert<HTMLSpanElement>(element, '.date');
assert.ok(dateEl);
dateEl.click();
assert.isTrue(stub.called);
assert.deepEqual(stub.lastCall.args[0].detail, {id: element.message?.id});
});
suite('uploaded patchset X message navigates to X - 1 vs X', () => {
let setUrlStub: SinonStubbedMember<NavigationService['setUrl']>;
setup(() => {
element.change = {...createChange(), revisions: createRevisions(4)};
setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl');
});
test('Patchset 1 navigates to Base', () => {
element.message = {
...createChangeMessage(),
message: 'Uploaded patch set 1.',
};
element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(setUrlStub.calledOnce);
assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/1');
});
test('Patchset X navigates to X vs X - 1', () => {
element.message = {
...createChangeMessage(),
message: 'Uploaded patch set 2.',
};
element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(setUrlStub.calledOnce);
assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/1..2');
element.message = {
...createChangeMessage(),
message: 'Uploaded patch set 200.',
};
element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(setUrlStub.calledTwice);
assert.equal(
setUrlStub.lastCall.firstArg,
'/c/test-project/+/42/199..200'
);
});
test('Commit message updated', () => {
element.message = {
...createChangeMessage(),
message: 'Commit message updated.',
};
element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(setUrlStub.calledOnce);
assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/3..4');
});
test('Merged patchset change message', () => {
element.message = {
...createChangeMessage(),
message: 'abcd↵3 is the latest approved patch-set.↵abc',
};
element.handleViewPatchsetDiff(new MouseEvent('click'));
assert.isTrue(setUrlStub.calledOnce);
assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/3..4');
});
});
suite('compute messages', () => {
const labels = {
'Code-Review': createLabelInfo(1),
'Code-Style': createLabelInfo(1),
};
test('empty', () => {
assert.equal(
element.computeMessageContent(
true,
'',
undefined,
'' as ReviewInputTag,
labels
),
''
);
assert.equal(
element.computeMessageContent(
false,
'',
undefined,
'' as ReviewInputTag,
labels
),
''
);
});
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.computeMessageContent(true, original, [], tag, labels)
);
assert.equal(actual, original);
actual = element.computeMessageContent(
false,
original,
[],
tag,
labels
);
assert.equal(actual, original);
});
test('new patchset rebased', () => {
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,
labels
);
assert.equal(actual, expected);
assert.equal(
actual,
element.computeMessageContent(true, original, [], tag)
);
actual = element.computeMessageContent(
false,
original,
[],
tag,
labels
);
assert.equal(actual, expected);
});
test('ready for review', () => {
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,
labels
);
assert.equal(actual, expected);
assert.equal(
actual,
element.computeMessageContent(true, original, [], tag)
);
actual = element.computeMessageContent(
false,
original,
[],
tag,
labels
);
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,
labels
);
assert.equal(actual, expected);
actual = element.computeMessageContent(
false,
original,
[],
tag,
labels
);
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,
labels
);
assert.equal(actual, expected);
actual = element.computeMessageContent(
false,
original,
[],
tag,
labels
);
assert.equal(actual, expected);
});
test('legacy change message', () => {
const original = 'Patch Set 1: Legacy Message';
const tag = undefined;
const expected = 'Legacy Message';
let actual = element.computeMessageContent(
true,
original,
[],
tag,
labels
);
assert.equal(actual, expected);
actual = element.computeMessageContent(
false,
original,
[],
tag,
labels
);
assert.equal(actual, expected);
});
test('comments', () => {
const original = 'Patch Set 1:\n\n(3 comments)';
const tag = undefined;
const expected = '';
let actual = element.computeMessageContent(
true,
original,
[],
tag,
labels
);
assert.equal(actual, expected);
actual = element.computeMessageContent(
false,
original,
[],
tag,
labels
);
assert.equal(actual, expected);
});
test('message template', () => {
const original =
'Removed vote: \n\n * Code-Style+1 by <GERRIT_ACCOUNT_0000001>\n * Code-Style-1 by <GERRIT_ACCOUNT_0000002>';
const tag = undefined;
const expected =
'Removed vote: \n\n * Code-Style+1 by User-1\n * Code-Style-1 by User-2';
const accountsInMessage = [
createAccountWithIdNameAndEmail(1),
createAccountWithIdNameAndEmail(2),
];
let actual = element.computeMessageContent(
true,
original,
accountsInMessage,
tag,
labels
);
assert.equal(actual, expected);
actual = element.computeMessageContent(
false,
original,
accountsInMessage,
tag,
labels
);
assert.equal(actual, expected);
});
test('message template missing accounts', () => {
const original =
'Removed vote: \n\n * Code-Style+1 by <GERRIT_ACCOUNT_0000001>\n * Code-Style-1 by <GERRIT_ACCOUNT_0000002>';
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,
labels
);
assert.equal(actual, expected);
actual = element.computeMessageContent(
false,
original,
[],
tag,
labels
);
assert.equal(actual, expected);
});
});
});
suite('when not logged in', () => {
setup(async () => {
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getIsAdmin').returns(Promise.resolve(false));
element = await fixture<GrMessage>(html`<gr-message></gr-message>`);
});
test('reply and delete button should be hidden', async () => {
element.message = {
...createChangeMessage(),
id: '47c43261_55aa2c41' as ChangeMessageId,
author: {
_account_id: 1115495 as AccountId,
name: 'Andrew Bonventre',
email: 'andybons@chromium.org' as EmailAddress,
},
date: '2016-01-12 20:24:49.448000000' as Timestamp,
message: 'Uploaded patch set 1.',
_revision_number: 1 as RevisionPatchSetNum,
expanded: true,
};
await element.updateComplete;
assert.isNotOk(query<HTMLElement>(element, '.replyActionContainer'));
assert.isNotOk(query<HTMLElement>(element, '.deleteBtn'));
});
});
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', () => {
element.commentThreads = [
{
comments: [
{
...createComment(),
change_message_id:
'6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3' as ChangeMessageId,
patch_set: 1 as RevisionPatchSetNum,
id: 'e365b138_bed65caa' as UrlEncodedCommentId,
updated: '2020-05-15 13:35:56.000000000' as Timestamp,
message: 'testing the load',
unresolved: false,
path: '/PATCHSET_LEVEL',
},
],
patchNum: 1 as RevisionPatchSetNum,
path: '/PATCHSET_LEVEL',
rootId: 'e365b138_bed65caa' as UrlEncodedCommentId,
commentSide: CommentSide.REVISION,
},
];
assert.equal(element.patchsetCommentSummary(), 'testing the load');
assert.equal(
element.computeMessageContent(false, '', undefined, undefined),
''
);
});
test('single patchset comment with reply', () => {
element.commentThreads = [
{
comments: [
{
...createComment(),
patch_set: 1 as RevisionPatchSetNum,
id: 'e365b138_bed65caa' as UrlEncodedCommentId,
updated: '2020-05-15 13:35:56.000000000' as Timestamp,
message: 'testing the load',
unresolved: false,
path: '/PATCHSET_LEVEL',
},
{
change_message_id: '6a07f64a82f96e7337ca5f7f84cfc73abf8ac2a3',
patch_set: 1 as RevisionPatchSetNum,
id: 'd6efcc85_4cbbb6f4' as UrlEncodedCommentId,
in_reply_to: 'e365b138_bed65caa' as UrlEncodedCommentId,
updated: '2020-05-15 16:55:28.000000000' as Timestamp,
message: 'n',
unresolved: false,
path: '/PATCHSET_LEVEL',
savingState: SavingState.OK,
},
],
patchNum: 1 as RevisionPatchSetNum,
path: '/PATCHSET_LEVEL',
rootId: 'e365b138_bed65caa' as UrlEncodedCommentId,
commentSide: CommentSide.REVISION,
},
];
assert.equal(element.patchsetCommentSummary(), 'n');
assert.equal(
element.computeMessageContent(false, '', undefined, undefined),
''
);
});
});
});