| /** |
| * @license |
| * Copyright 2020 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import * as sinon from 'sinon'; |
| import '../../../test/common-test-setup'; |
| import './gr-change-metadata'; |
| |
| import {ChangeRole, GrChangeMetadata} from './gr-change-metadata'; |
| import { |
| createServerInfo, |
| createParsedChange, |
| createAccountWithId, |
| createCommitInfoWithRequiredCommit, |
| createWebLinkInfo, |
| createGerritInfo, |
| createGitPerson, |
| createCommit, |
| createRevision, |
| createAccountDetailWithId, |
| createConfig, |
| } from '../../../test/test-data-generators'; |
| import { |
| ChangeStatus, |
| SubmitType, |
| GpgKeyInfoStatus, |
| InheritedBooleanInfoConfiguredValue, |
| } from '../../../constants/constants'; |
| import { |
| EmailAddress, |
| AccountId, |
| CommitId, |
| ServerInfo, |
| RevisionInfo, |
| ParentCommitInfo, |
| TopicName, |
| RevisionPatchSetNum, |
| NumericChangeId, |
| LabelValueToDescriptionMap, |
| Hashtag, |
| CommitInfo, |
| } from '../../../types/common'; |
| import {GrEditableLabel} from '../../shared/gr-editable-label/gr-editable-label'; |
| import {PluginApi} from '../../../api/plugin'; |
| import {GrEndpointDecorator} from '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator'; |
| import { |
| queryAndAssert, |
| stubRestApi, |
| waitUntilCalled, |
| } from '../../../test/test-utils'; |
| import {ParsedChangeInfo} from '../../../types/types'; |
| import {GrLinkedChip} from '../../shared/gr-linked-chip/gr-linked-chip'; |
| import {GrButton} from '../../shared/gr-button/gr-button'; |
| import {nothing} from 'lit'; |
| import {fixture, html, assert} from '@open-wc/testing'; |
| import {testResolver} from '../../../test/common-test-setup'; |
| import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader'; |
| |
| suite('gr-change-metadata tests', () => { |
| let element: GrChangeMetadata; |
| |
| setup(async () => { |
| element = await fixture(html`<gr-change-metadata></gr-change-metadata>`); |
| element.change = createParsedChange(); |
| element.account = undefined; |
| await element.updateComplete; |
| }); |
| |
| test('renders', async () => { |
| await element.updateComplete; |
| assert.shadowDom.equal( |
| element, |
| /* HTML */ `<div> |
| <div class="metadata-header"> |
| <h3 class="heading-3 metadata-title">Change Info</h3> |
| <gr-button |
| class="show-all-button" |
| link="" |
| role="button" |
| tabindex="0" |
| aria-disabled="false" |
| > |
| Show all <gr-icon icon="expand_more"></gr-icon> |
| <gr-icon hidden="" icon="expand_less"></gr-icon> |
| </gr-button> |
| </div> |
| <section class="hideDisplay"> |
| <span class="title"> |
| <gr-tooltip-content |
| has-tooltip="" |
| title="Last update of (meta)data for this change." |
| > |
| Updated |
| </gr-tooltip-content> |
| </span> |
| <span class="value"> |
| <gr-date-formatter showyesterday="" withtooltip=""> |
| </gr-date-formatter> |
| </span> |
| </section> |
| <section> |
| <span class="title"> |
| <gr-tooltip-content |
| has-tooltip="" |
| title="This user created or uploaded the first patchset of this change." |
| > |
| Owner |
| </gr-tooltip-content> |
| </span> |
| <span class="value"> |
| <gr-account-chip highlightattention="" |
| ><gr-vote-chip circle-shape="" slot="vote-chip"> </gr-vote-chip |
| ></gr-account-chip> |
| </span> |
| </section> |
| <section> |
| <span class="title"> |
| <gr-tooltip-content |
| has-tooltip="" |
| title="This user wrote the code change." |
| > |
| Author |
| </gr-tooltip-content> |
| </span> |
| <span class="value"> |
| <gr-account-chip><gr-vote-chip circle-shape="" slot="vote-chip"></gr-vote-chip |
| ></gr-account-chip> |
| </span> |
| </section> |
| <section> |
| <span class="title"> |
| <gr-tooltip-content |
| has-tooltip="" |
| title="This user committed the code change to the Git repository (typically to the local Git repo before uploading)." |
| > |
| Committer |
| </gr-tooltip-content> |
| </span> |
| <span class="value"> |
| <gr-account-chip><gr-vote-chip circle-shape="" slot="vote-chip"></gr-account-chip> |
| </span> |
| </section> |
| <section> |
| <span class="title"> Reviewers </span> |
| <span class="value"> |
| <gr-reviewer-list reviewers-only=""> </gr-reviewer-list> |
| </span> |
| </section> |
| <section class="hideDisplay"> |
| <span class="title"> CC </span> |
| <span class="value"> |
| <gr-reviewer-list ccs-only=""> </gr-reviewer-list> |
| </span> |
| </section> |
| <section> |
| <span class="title"> |
| <gr-tooltip-content |
| has-tooltip="" |
| title="Repository and branch that the change will be merged into if submitted." |
| > |
| Repo | Branch |
| </gr-tooltip-content> |
| </span> |
| <span class="value"> |
| <a href="/q/project:test-project"> |
| test-project |
| </a> |
| | |
| <a href="/q/project:test-project+branch:test-branch"> |
| test-branch |
| </a> |
| </span> |
| </section> |
| <section class="hideDisplay"> |
| <span class="title">Parent</span> |
| <span class="value"> |
| <ol class="nonMerge notCurrent parentList"></ol> |
| </span> |
| </section> |
| <section class="hideDisplay strategy"> |
| <span class="title"> Strategy </span> <span class="value"> </span> |
| </section> |
| <section class="hashtag hideDisplay"> |
| <span class="title"> Hashtags </span> |
| <span class="value"> </span> |
| </section> |
| <gr-endpoint-decorator name="change-metadata-item"> |
| <gr-endpoint-param name="labels"> </gr-endpoint-param> |
| <gr-endpoint-param name="change"> </gr-endpoint-param> |
| <gr-endpoint-param name="revision"> </gr-endpoint-param> |
| <gr-endpoint-slot name="above-submit-requirements"></gr-endpoint-slot> |
| <div class="separatedSection"> |
| <gr-submit-requirements></gr-submit-requirements> |
| </div> |
| </gr-endpoint-decorator> |
| </div>` |
| ); |
| }); |
| |
| test('computeMergedCommitInfo', () => { |
| const dummyRevs: {[revisionId: string]: RevisionInfo} = { |
| 1: createRevision(1), |
| 2: createRevision(2), |
| }; |
| assert.deepEqual( |
| element.computeMergedCommitInfo('0' as CommitId, dummyRevs), |
| undefined |
| ); |
| assert.deepEqual( |
| element.computeMergedCommitInfo('1' as CommitId, dummyRevs), |
| dummyRevs[1].commit |
| ); |
| |
| // Regression test for issue 5337. |
| const commit = element.computeMergedCommitInfo('2' as CommitId, dummyRevs); |
| assert.notDeepEqual(commit, dummyRevs[2] as unknown as CommitInfo); |
| assert.deepEqual(commit, dummyRevs[2].commit); |
| }); |
| |
| test('show strategy for open change', async () => { |
| element.change = { |
| ...createParsedChange(), |
| status: ChangeStatus.NEW, |
| submit_type: SubmitType.CHERRY_PICK, |
| labels: {}, |
| }; |
| await element.updateComplete; |
| const strategy = element.shadowRoot?.querySelector('.strategy'); |
| assert.ok(strategy); |
| assert.isFalse(strategy?.hasAttribute('hidden')); |
| assert.equal(strategy?.children[1].textContent, 'Cherry Pick'); |
| }); |
| |
| test('hide strategy for closed change', async () => { |
| element.change = { |
| ...createParsedChange(), |
| status: ChangeStatus.MERGED, |
| labels: {}, |
| }; |
| await element.updateComplete; |
| assert.isNull(element.shadowRoot?.querySelector('.strategy')); |
| }); |
| |
| test('weblinks hidden when no weblinks', async () => { |
| element.revision = { |
| ...createRevision(), |
| commit: createCommitInfoWithRequiredCommit(), |
| }; |
| element.serverConfig = createServerInfo(); |
| await element.updateComplete; |
| assert.isNull(element.webLinks); |
| }); |
| |
| test('weblinks hidden when only gitiles weblink', async () => { |
| element.revision = { |
| ...createRevision(), |
| commit: { |
| ...createCommitInfoWithRequiredCommit(), |
| web_links: [{...createWebLinkInfo(), name: 'gitiles', url: '#'}], |
| }, |
| }; |
| element.serverConfig = createServerInfo(); |
| await element.updateComplete; |
| assert.isNull(element.webLinks); |
| assert.equal(element.computeWebLinks().length, 0); |
| }); |
| |
| test('weblinks hidden when sole weblink is set as primary', async () => { |
| const browser = 'browser'; |
| element.revision = { |
| ...createRevision(), |
| commit: { |
| ...createCommitInfoWithRequiredCommit(), |
| web_links: [{...createWebLinkInfo(), name: browser, url: '#'}], |
| }, |
| }; |
| element.serverConfig = { |
| ...createServerInfo(), |
| gerrit: { |
| ...createGerritInfo(), |
| primary_weblink_name: browser, |
| }, |
| }; |
| await element.updateComplete; |
| assert.isNull(element.webLinks); |
| }); |
| |
| test('weblinks are visible when other weblinks', async () => { |
| element.revision = { |
| ...createRevision(), |
| commit: { |
| ...createCommitInfoWithRequiredCommit(), |
| web_links: [{...createWebLinkInfo(), name: 'test', url: '#'}], |
| }, |
| }; |
| await element.updateComplete; |
| const webLinks = element.webLinks!; |
| assert.isFalse(webLinks.hasAttribute('hidden')); |
| assert.equal(element.computeWebLinks().length, 1); |
| }); |
| |
| test('weblinks are visible when gitiles and other weblinks', async () => { |
| element.revision = { |
| ...createRevision(), |
| commit: { |
| ...createCommitInfoWithRequiredCommit(), |
| web_links: [ |
| {...createWebLinkInfo(), name: 'test', url: '#'}, |
| {...createWebLinkInfo(), name: 'gitiles', url: '#'}, |
| ], |
| }, |
| }; |
| await element.updateComplete; |
| const webLinks = element.webLinks!; |
| assert.isFalse(webLinks.hasAttribute('hidden')); |
| // Only the non-gitiles weblink is returned. |
| assert.equal(element.computeWebLinks().length, 1); |
| }); |
| |
| suite('getNonOwnerRole', () => { |
| let change: ParsedChangeInfo | undefined; |
| |
| setup(() => { |
| change = { |
| ...createParsedChange(), |
| owner: { |
| ...createAccountWithId(), |
| email: 'abc@def' as EmailAddress, |
| _account_id: 1019328 as AccountId, |
| }, |
| revisions: { |
| rev1: { |
| ...createRevision(), |
| uploader: { |
| ...createAccountWithId(), |
| email: 'ghi@def' as EmailAddress, |
| _account_id: 1011123 as AccountId, |
| }, |
| commit: { |
| ...createCommit(), |
| author: {...createGitPerson(), email: 'jkl@def' as EmailAddress}, |
| committer: { |
| ...createGitPerson(), |
| email: 'ghi@def' as EmailAddress, |
| }, |
| }, |
| }, |
| }, |
| current_revision: 'rev1' as CommitId, |
| }; |
| }); |
| |
| suite('role=uploader', () => { |
| test('getNonOwnerRole for uploader', () => { |
| element.change = change; |
| assert.deepEqual(element.getNonOwnerRole(ChangeRole.UPLOADER), { |
| ...createAccountWithId(), |
| email: 'ghi@def' as EmailAddress, |
| _account_id: 1011123 as AccountId, |
| }); |
| }); |
| |
| test('getNonOwnerRole that it does not return uploader', () => { |
| // Set the uploader email to be the same as the owner. |
| change!.revisions.rev1.uploader!._account_id = 1019328 as AccountId; |
| element.change = change; |
| assert.isNotOk(element.getNonOwnerRole(ChangeRole.UPLOADER)); |
| }); |
| |
| test('computeShowRoleClass show uploader', () => { |
| element.change = change; |
| assert.notEqual(element.renderNonOwner(ChangeRole.UPLOADER), nothing); |
| }); |
| |
| test('computeShowRoleClass hide uploader', () => { |
| // Set the uploader email to be the same as the owner. |
| change!.revisions.rev1.uploader!._account_id = 1019328 as AccountId; |
| element.change = change; |
| assert.equal(element.renderNonOwner(ChangeRole.UPLOADER), nothing); |
| }); |
| }); |
| |
| suite('role=committer', () => { |
| test('getNonOwnerRole for committer', () => { |
| change!.revisions.rev1.uploader!.email = 'ghh@def' as EmailAddress; |
| element.change = change; |
| assert.deepEqual(element.getNonOwnerRole(ChangeRole.COMMITTER), { |
| ...createGitPerson(), |
| email: 'ghi@def' as EmailAddress, |
| }); |
| }); |
| |
| test('getNonOwnerRole is null if committer is same as uploader', () => { |
| element.change = change; |
| assert.isNotOk(element.getNonOwnerRole(ChangeRole.COMMITTER)); |
| }); |
| |
| test('getNonOwnerRole that it does not return committer', () => { |
| // Set the committer email to be the same as the owner. |
| change!.revisions.rev1.commit!.committer.email = |
| 'abc@def' as EmailAddress; |
| element.change = change; |
| assert.isNotOk(element.getNonOwnerRole(ChangeRole.COMMITTER)); |
| }); |
| |
| test('getNonOwnerRole null for committer with no commit', () => { |
| delete change!.revisions.rev1.commit; |
| element.change = change; |
| assert.isNotOk(element.getNonOwnerRole(ChangeRole.COMMITTER)); |
| }); |
| |
| test('getNonOwnerRole returns committer with same email as owner in edit mode', () => { |
| // Set the committer email to be the same as the owner. |
| change!.revisions.rev1.commit!.committer.email = |
| 'abc@def' as EmailAddress; |
| element.change = change; |
| element.editMode = true; |
| assert.isOk(element.getNonOwnerRole(ChangeRole.COMMITTER)); |
| }); |
| }); |
| |
| suite('role=author', () => { |
| test('getNonOwnerRole for author', () => { |
| element.change = change; |
| assert.deepEqual(element.getNonOwnerRole(ChangeRole.AUTHOR), { |
| ...createGitPerson(), |
| email: 'jkl@def' as EmailAddress, |
| }); |
| }); |
| |
| test('getNonOwnerRole that it does not return author', () => { |
| // Set the author email to be the same as the owner. |
| change!.revisions.rev1.commit!.author.email = 'abc@def' as EmailAddress; |
| element.change = change; |
| assert.isNotOk(element.getNonOwnerRole(ChangeRole.AUTHOR)); |
| }); |
| |
| test('getNonOwnerRole null for author with no commit', () => { |
| delete change!.revisions.rev1.commit; |
| element.change = change; |
| assert.isNotOk(element.getNonOwnerRole(ChangeRole.AUTHOR)); |
| }); |
| |
| test('getNonOwnerRole returns author with same email as owner in edit mode', () => { |
| // Set the author email to be the same as the owner. |
| change!.revisions.rev1.commit!.author.email = 'abc@def' as EmailAddress; |
| element.change = change; |
| element.editMode = true; |
| assert.isOk(element.getNonOwnerRole(ChangeRole.AUTHOR)); |
| }); |
| }); |
| }); |
| |
| suite('Push Certificate Validation', () => { |
| let serverConfig: ServerInfo | undefined; |
| let change: ParsedChangeInfo | undefined; |
| |
| setup(() => { |
| serverConfig = { |
| ...createServerInfo(), |
| receive: { |
| enable_signed_push: 'true', |
| }, |
| }; |
| change = { |
| ...createParsedChange(), |
| revisions: { |
| rev1: { |
| ...createRevision(1), |
| push_certificate: { |
| certificate: 'Push certificate', |
| key: { |
| status: GpgKeyInfoStatus.BAD, |
| problems: ['No public keys found for key ID E5E20E52'], |
| }, |
| }, |
| }, |
| }, |
| current_revision: 'rev1' as CommitId, |
| status: ChangeStatus.NEW, |
| labels: {}, |
| mergeable: true, |
| }; |
| element.repoConfig = { |
| ...createConfig(), |
| enable_signed_push: { |
| configured_value: 'TRUE' as InheritedBooleanInfoConfiguredValue, |
| value: true, |
| }, |
| }; |
| }); |
| |
| test('Push Certificate Validation test BAD', () => { |
| change!.revisions.rev1.push_certificate = { |
| certificate: 'Push certificate', |
| key: { |
| status: GpgKeyInfoStatus.BAD, |
| problems: ['No public keys found for key ID E5E20E52'], |
| }, |
| }; |
| element.change = change; |
| element.serverConfig = serverConfig; |
| const result = element.computePushCertificateValidation(); |
| assert.equal( |
| result?.message, |
| 'Push certificate is invalid:\n' + |
| 'No public keys found for key ID E5E20E52' |
| ); |
| assert.equal(result?.icon, 'close'); |
| assert.equal(result?.class, 'invalid'); |
| }); |
| |
| test('Push Certificate Validation test TRUSTED', () => { |
| change!.revisions.rev1.push_certificate = { |
| certificate: 'Push certificate', |
| key: { |
| status: GpgKeyInfoStatus.TRUSTED, |
| }, |
| }; |
| element.change = change; |
| element.serverConfig = serverConfig; |
| const result = element.computePushCertificateValidation(); |
| assert.equal( |
| result?.message, |
| 'Push certificate is valid and key is trusted' |
| ); |
| assert.equal(result?.icon, 'check'); |
| assert.equal(result?.class, 'trusted'); |
| }); |
| |
| test('Push Certificate Validation is missing test', () => { |
| change!.revisions.rev1 = createRevision(1); |
| element.change = change; |
| element.serverConfig = serverConfig; |
| const result = element.computePushCertificateValidation(); |
| assert.equal( |
| result?.message, |
| 'This patch set was created without a push certificate' |
| ); |
| assert.equal(result?.icon, 'help'); |
| assert.equal(result?.class, 'help filled'); |
| }); |
| |
| test('computePushCertificateValidation returns undefined', () => { |
| element.change = change; |
| delete serverConfig!.receive!.enable_signed_push; |
| element.serverConfig = serverConfig; |
| assert.isUndefined(element.computePushCertificateValidation()); |
| }); |
| |
| test('isEnabledSignedPushOnRepo', () => { |
| change!.revisions.rev1.push_certificate = { |
| certificate: 'Push certificate', |
| key: { |
| status: GpgKeyInfoStatus.TRUSTED, |
| }, |
| }; |
| element.change = change; |
| element.serverConfig = serverConfig; |
| element.repoConfig!.enable_signed_push!.configured_value = |
| InheritedBooleanInfoConfiguredValue.INHERIT; |
| element.repoConfig!.enable_signed_push!.inherited_value = true; |
| assert.isTrue(element.isEnabledSignedPushOnRepo()); |
| |
| element.repoConfig!.enable_signed_push!.inherited_value = false; |
| assert.isFalse(element.isEnabledSignedPushOnRepo()); |
| |
| element.repoConfig!.enable_signed_push!.configured_value = |
| InheritedBooleanInfoConfiguredValue.TRUE; |
| assert.isTrue(element.isEnabledSignedPushOnRepo()); |
| |
| element.repoConfig = undefined; |
| assert.isFalse(element.isEnabledSignedPushOnRepo()); |
| }); |
| }); |
| |
| test('computeParents', () => { |
| const parents: ParentCommitInfo[] = [ |
| {...createCommit(), commit: '123' as CommitId, subject: 'abc'}, |
| ]; |
| const revision: RevisionInfo = { |
| ...createRevision(1), |
| commit: {...createCommit(), parents}, |
| }; |
| element.change = undefined; |
| element.revision = revision; |
| assert.equal(element.computeParents(), parents); |
| const change = (current_revision: CommitId): ParsedChangeInfo => { |
| return { |
| ...createParsedChange(), |
| current_revision, |
| revisions: {456: revision}, |
| }; |
| }; |
| const changebadrevision = change('789' as CommitId); |
| element.change = changebadrevision; |
| element.revision = createRevision(); |
| assert.deepEqual(element.computeParents(), []); |
| const changenocommit: ParsedChangeInfo = { |
| ...createParsedChange(), |
| current_revision: '456' as CommitId, |
| revisions: {456: createRevision()}, |
| }; |
| element.change = changenocommit; |
| element.revision = undefined; |
| assert.deepEqual(element.computeParents(), []); |
| const changegood = change('456' as CommitId); |
| element.change = changegood; |
| element.revision = undefined; |
| assert.equal(element.computeParents(), parents); |
| }); |
| |
| test('currentParents', async () => { |
| const revision = (parent: CommitId): RevisionInfo => { |
| return { |
| ...createRevision(), |
| commit: { |
| ...createCommit(), |
| parents: [{...createCommit(), commit: parent, subject: 'abc'}], |
| }, |
| }; |
| }; |
| element.change = { |
| ...createParsedChange(), |
| current_revision: '456' as CommitId, |
| revisions: {456: revision('111' as CommitId)}, |
| owner: {}, |
| }; |
| element.revision = revision('222' as CommitId); |
| await element.updateComplete; |
| assert.equal(element.currentParents[0].commit, '222'); |
| element.revision = revision('333' as CommitId); |
| await element.updateComplete; |
| assert.equal(element.currentParents[0].commit, '333'); |
| element.revision = undefined; |
| await element.updateComplete; |
| assert.equal(element.currentParents[0].commit, '111'); |
| element.change = createParsedChange(); |
| await element.updateComplete; |
| assert.deepEqual(element.currentParents, []); |
| }); |
| |
| test('computeParentListClass', () => { |
| const parent: ParentCommitInfo = { |
| ...createCommit(), |
| commit: 'abc123' as CommitId, |
| subject: 'My parent commit', |
| }; |
| element.currentParents = [parent]; |
| element.parentIsCurrent = true; |
| assert.equal( |
| element.computeParentListClass(), |
| 'parentList nonMerge current' |
| ); |
| element.currentParents = [parent]; |
| element.parentIsCurrent = false; |
| assert.equal( |
| element.computeParentListClass(), |
| 'parentList nonMerge notCurrent' |
| ); |
| element.currentParents = [parent, parent]; |
| element.parentIsCurrent = false; |
| assert.equal( |
| element.computeParentListClass(), |
| 'parentList merge notCurrent' |
| ); |
| element.currentParents = [parent, parent]; |
| element.parentIsCurrent = true; |
| assert.equal(element.computeParentListClass(), 'parentList merge current'); |
| }); |
| |
| test('showAddTopic', () => { |
| const change = createParsedChange(); |
| element.change = undefined; |
| element.settingTopic = false; |
| element.topicReadOnly = false; |
| assert.isTrue(element.showAddTopic()); |
| // do not show for 'readonly' |
| element.change = undefined; |
| element.settingTopic = false; |
| element.topicReadOnly = true; |
| assert.isFalse(element.showAddTopic()); |
| element.change = change; |
| element.settingTopic = false; |
| element.topicReadOnly = false; |
| assert.isTrue(element.showAddTopic()); |
| element.change = change; |
| element.settingTopic = true; |
| element.topicReadOnly = false; |
| assert.isFalse(element.showAddTopic()); |
| change.topic = 'foo' as TopicName; |
| element.change = change; |
| element.settingTopic = true; |
| element.topicReadOnly = false; |
| assert.isFalse(element.showAddTopic()); |
| element.change = change; |
| element.settingTopic = false; |
| element.topicReadOnly = false; |
| assert.isFalse(element.showAddTopic()); |
| }); |
| |
| test('showTopicChip', async () => { |
| const change = createParsedChange(); |
| element.change = change; |
| element.settingTopic = true; |
| await element.updateComplete; |
| assert.isFalse(element.showTopicChip()); |
| element.change = change; |
| element.settingTopic = false; |
| await element.updateComplete; |
| assert.isFalse(element.showTopicChip()); |
| element.change = change; |
| element.settingTopic = true; |
| await element.updateComplete; |
| assert.isFalse(element.showTopicChip()); |
| change.topic = 'foo' as TopicName; |
| element.change = change; |
| element.settingTopic = true; |
| await element.updateComplete; |
| assert.isFalse(element.showTopicChip()); |
| element.change = change; |
| element.settingTopic = false; |
| await element.updateComplete; |
| assert.isTrue(element.showTopicChip()); |
| }); |
| |
| test('showCherryPickOf', async () => { |
| element.change = undefined; |
| await element.updateComplete; |
| assert.isFalse(element.showCherryPickOf()); |
| const change = createParsedChange(); |
| element.change = change; |
| await element.updateComplete; |
| assert.isFalse(element.showCherryPickOf()); |
| change.cherry_pick_of_change = 123 as NumericChangeId; |
| change.cherry_pick_of_patch_set = 1 as RevisionPatchSetNum; |
| element.change = change; |
| await element.updateComplete; |
| assert.isTrue(element.showCherryPickOf()); |
| }); |
| |
| suite('Topic removal', () => { |
| let change: ParsedChangeInfo; |
| setup(() => { |
| change = { |
| ...createParsedChange(), |
| actions: { |
| topic: {enabled: false}, |
| }, |
| topic: 'the topic' as TopicName, |
| status: ChangeStatus.NEW, |
| submit_type: SubmitType.CHERRY_PICK, |
| labels: { |
| test: { |
| all: [{_account_id: 1 as AccountId, name: 'bojack', value: 1}], |
| default_value: 0, |
| values: [] as unknown as LabelValueToDescriptionMap, |
| }, |
| }, |
| removable_reviewers: [], |
| }; |
| }); |
| |
| test('computeTopicReadOnly', () => { |
| let mutable = false; |
| element.mutable = mutable; |
| element.change = change; |
| assert.isTrue(element.computeTopicReadOnly()); |
| mutable = true; |
| element.mutable = mutable; |
| assert.isTrue(element.computeTopicReadOnly()); |
| change.actions!.topic!.enabled = true; |
| element.mutable = mutable; |
| element.change = change; |
| assert.isFalse(element.computeTopicReadOnly()); |
| mutable = false; |
| element.mutable = mutable; |
| assert.isTrue(element.computeTopicReadOnly()); |
| }); |
| |
| test('topic read only hides delete button', async () => { |
| element.account = createAccountDetailWithId(); |
| element.change = change; |
| await element.updateComplete; |
| const chip = queryAndAssert<GrLinkedChip>(element, 'gr-linked-chip'); |
| const button = queryAndAssert<GrButton>(chip, 'gr-button'); |
| assert.isTrue(button.hasAttribute('hidden')); |
| }); |
| |
| test('topic not read only does not hide delete button', async () => { |
| element.account = createAccountDetailWithId(); |
| change.actions!.topic!.enabled = true; |
| element.change = change; |
| await element.updateComplete; |
| const chip = queryAndAssert<GrLinkedChip>(element, 'gr-linked-chip'); |
| const button = queryAndAssert<GrButton>(chip, 'gr-button'); |
| assert.isFalse(button.hasAttribute('hidden')); |
| }); |
| }); |
| |
| suite('Hashtag removal', () => { |
| let change: ParsedChangeInfo; |
| setup(() => { |
| change = { |
| ...createParsedChange(), |
| actions: { |
| hashtags: {enabled: false}, |
| }, |
| hashtags: ['test-hashtag' as Hashtag], |
| labels: { |
| test: { |
| all: [{_account_id: 1 as AccountId, name: 'bojack', value: 1}], |
| default_value: 0, |
| values: [] as unknown as LabelValueToDescriptionMap, |
| }, |
| }, |
| removable_reviewers: [], |
| }; |
| }); |
| |
| test('computeHashtagReadOnly', async () => { |
| await element.updateComplete; |
| let mutable = false; |
| element.change = change; |
| element.mutable = mutable; |
| await element.updateComplete; |
| assert.isTrue(element.computeHashtagReadOnly()); |
| mutable = true; |
| element.change = change; |
| element.mutable = mutable; |
| await element.updateComplete; |
| assert.isTrue(element.computeHashtagReadOnly()); |
| change.actions!.hashtags!.enabled = true; |
| element.change = change; |
| element.mutable = mutable; |
| await element.updateComplete; |
| assert.isFalse(element.computeHashtagReadOnly()); |
| mutable = false; |
| element.change = change; |
| element.mutable = mutable; |
| await element.updateComplete; |
| assert.isTrue(element.computeHashtagReadOnly()); |
| }); |
| |
| test('hashtag read only hides delete button', async () => { |
| element.account = createAccountDetailWithId(); |
| element.change = change; |
| await element.updateComplete; |
| assert.isTrue(element.mutable, 'Mutable'); |
| assert.isFalse( |
| element.change.actions?.hashtags?.enabled, |
| 'hashtags disabled' |
| ); |
| assert.isTrue(element.hashtagReadOnly, 'hashtag read only'); |
| const chip = queryAndAssert<GrLinkedChip>(element, 'gr-linked-chip'); |
| const button = queryAndAssert<GrButton>(chip, 'gr-button'); |
| assert.isTrue(button.hasAttribute('hidden'), 'button hidden'); |
| }); |
| |
| test('hashtag not read only does not hide delete button', async () => { |
| await element.updateComplete; |
| element.account = createAccountDetailWithId(); |
| change.actions!.hashtags!.enabled = true; |
| element.change = change; |
| await element.updateComplete; |
| const chip = queryAndAssert<GrLinkedChip>(element, 'gr-linked-chip'); |
| const button = queryAndAssert<GrButton>(chip, 'gr-button'); |
| assert.isFalse(button.hasAttribute('hidden')); |
| }); |
| }); |
| |
| suite('remove reviewer votes', () => { |
| setup(async () => { |
| sinon.stub(element, 'computeTopicReadOnly').returns(true); |
| element.change = { |
| ...createParsedChange(), |
| topic: 'the topic' as TopicName, |
| labels: { |
| test: { |
| all: [{_account_id: 1 as AccountId, name: 'bojack', value: 1}], |
| default_value: 0, |
| values: [] as unknown as LabelValueToDescriptionMap, |
| }, |
| }, |
| removable_reviewers: [], |
| }; |
| await element.updateComplete; |
| }); |
| |
| test('changing topic', async () => { |
| const newTopic = 'the new topic' as TopicName; |
| const setChangeTopicStub = stubRestApi('setChangeTopic').returns( |
| Promise.resolve(newTopic) |
| ); |
| const alertStub = sinon.stub(); |
| element.addEventListener('show-alert', alertStub); |
| |
| element.handleTopicChanged(new CustomEvent('test', {detail: newTopic})); |
| |
| assert.isTrue( |
| setChangeTopicStub.calledWith(42 as NumericChangeId, newTopic) |
| ); |
| await setChangeTopicStub.lastCall.returnValue; |
| await waitUntilCalled(alertStub, 'alertStub'); |
| assert.deepEqual(alertStub.lastCall.args[0].detail, { |
| message: 'Saving topic and reloading ...', |
| showDismiss: true, |
| }); |
| }); |
| |
| test('topic removal', async () => { |
| const newTopic = 'the new topic' as TopicName; |
| const setChangeTopicStub = stubRestApi('setChangeTopic').returns( |
| Promise.resolve(newTopic) |
| ); |
| const alertStub = sinon.stub(); |
| element.addEventListener('show-alert', alertStub); |
| await element.updateComplete; |
| const chip = queryAndAssert<GrLinkedChip>(element, 'gr-linked-chip'); |
| const remove = queryAndAssert<GrButton>(chip, '#remove'); |
| |
| remove.click(); |
| |
| assert.isTrue(chip?.disabled); |
| assert.isTrue(setChangeTopicStub.calledWith(42 as NumericChangeId)); |
| await setChangeTopicStub.lastCall.returnValue; |
| await waitUntilCalled(alertStub, 'alertStub'); |
| assert.deepEqual(alertStub.lastCall.args[0].detail, { |
| message: 'Removing topic and reloading ...', |
| showDismiss: true, |
| }); |
| }); |
| |
| test('changing hashtag', async () => { |
| await element.updateComplete; |
| const newHashtag: Hashtag[] = ['new hashtag' as Hashtag]; |
| const setChangeHashtagStub = stubRestApi('setChangeHashtag').returns( |
| Promise.resolve(newHashtag) |
| ); |
| const alertStub = sinon.stub(); |
| element.addEventListener('show-alert', alertStub); |
| element.handleHashtagChanged( |
| new CustomEvent('test', {detail: 'new hashtag'}) |
| ); |
| assert.isTrue( |
| setChangeHashtagStub.calledWith(42 as NumericChangeId, { |
| add: ['new hashtag' as Hashtag], |
| }) |
| ); |
| await setChangeHashtagStub.lastCall.returnValue; |
| await waitUntilCalled(alertStub, 'alertStub'); |
| assert.deepEqual(alertStub.lastCall.args[0].detail, { |
| message: 'Saving hashtag and reloading ...', |
| showDismiss: true, |
| }); |
| }); |
| }); |
| |
| test('update author identity', async () => { |
| const change = createParsedChange(); |
| element.change = change; |
| element.editMode = true; |
| await element.updateComplete; |
| const updateIdentityInChangeEditStub = stubRestApi( |
| 'updateIdentityInChangeEdit' |
| ).resolves(); |
| const alertStub = sinon.stub(); |
| element.addEventListener('show-alert', alertStub); |
| queryAndAssert(element, '#author-edit-label').dispatchEvent( |
| new CustomEvent('changed', {detail: 'user <user@example.com>'}) |
| ); |
| assert.isTrue( |
| updateIdentityInChangeEditStub.calledWith( |
| 42 as NumericChangeId, |
| 'user', |
| 'user@example.com', |
| 'AUTHOR' |
| ) |
| ); |
| await updateIdentityInChangeEditStub.lastCall.returnValue; |
| await waitUntilCalled(alertStub, 'alertStub'); |
| assert.deepEqual(alertStub.lastCall.args[0].detail, { |
| message: 'Saving identity and reloading ...', |
| showDismiss: true, |
| }); |
| }); |
| |
| test('editTopic', async () => { |
| element.account = createAccountDetailWithId(); |
| element.change = { |
| ...createParsedChange(), |
| actions: {topic: {enabled: true}}, |
| }; |
| await element.updateComplete; |
| |
| const label = element.shadowRoot!.querySelector( |
| '.topicEditableLabel' |
| ) as GrEditableLabel; |
| assert.ok(label); |
| const openStub = sinon.stub(label, 'open'); |
| element.editTopic(); |
| await element.updateComplete; |
| |
| assert.isTrue(openStub.called); |
| }); |
| |
| suite('plugin endpoints', () => { |
| setup(async () => { |
| element = await fixture(html`<gr-change-metadata></gr-change-metadata>`); |
| element.change = createParsedChange(); |
| element.revision = createRevision(); |
| await element.updateComplete; |
| }); |
| |
| test('endpoint params', async () => { |
| interface MetadataGrEndpointDecorator extends GrEndpointDecorator { |
| plugin: PluginApi; |
| change: ParsedChangeInfo; |
| revision: RevisionInfo; |
| } |
| let plugin: PluginApi; |
| window.Gerrit.install( |
| p => { |
| plugin = p; |
| }, |
| '0.1', |
| 'http://some/plugins/url.js' |
| ); |
| await element.updateComplete; |
| const hookEl = (await plugin! |
| .hook('change-metadata-item') |
| .getLastAttached()) as MetadataGrEndpointDecorator; |
| testResolver(pluginLoaderToken).loadPlugins([]); |
| await element.updateComplete; |
| assert.strictEqual(hookEl.plugin, plugin!); |
| assert.strictEqual(hookEl.change, element.change); |
| assert.strictEqual(hookEl.revision, element.revision); |
| }); |
| }); |
| }); |