| /** |
| * @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), |
| '' |
| ); |
| }); |
| }); |
| }); |