Merge changes from topic "gr-change-metadata-test-to-ts"
* changes:
Tests gr-change-metadata to ts
Rename gr-change-metadata_test to ts
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index d192060..9910fb6 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -109,7 +109,7 @@
const NOT_CURRENT_MESSAGE = 'Not current - rebase possible';
-interface PushCertifacteValidationInfo {
+interface PushCertificateValidationInfo {
class: string;
icon: string;
message: string;
@@ -118,6 +118,7 @@
export interface GrChangeMetadata {
$: {
restAPI: RestApiService & Element;
+ webLinks: HTMLElement;
};
}
@@ -178,7 +179,7 @@
type: Object,
computed: '_computePushCertificateValidation(serverConfig, change)',
})
- _pushCertificateValidation?: PushCertifacteValidationInfo;
+ _pushCertificateValidation?: PushCertificateValidationInfo;
@property({type: Boolean, computed: '_computeShowRequirements(change)'})
_showRequirements = false;
@@ -323,7 +324,7 @@
}
_showAddTopic(
- changeRecord: ElementPropertyDeepChange<GrChangeMetadata, 'change'>,
+ changeRecord?: ElementPropertyDeepChange<GrChangeMetadata, 'change'>,
settingTopic?: boolean
) {
const hasTopic = !!changeRecord?.base?.topic;
@@ -331,7 +332,7 @@
}
_showTopicChip(
- changeRecord: ElementPropertyDeepChange<GrChangeMetadata, 'change'>,
+ changeRecord?: ElementPropertyDeepChange<GrChangeMetadata, 'change'>,
settingTopic?: boolean
) {
const hasTopic = !!changeRecord?.base?.topic;
@@ -339,7 +340,7 @@
}
_showCherryPickOf(
- changeRecord: ElementPropertyDeepChange<GrChangeMetadata, 'change'>
+ changeRecord?: ElementPropertyDeepChange<GrChangeMetadata, 'change'>
) {
const hasCherryPickOf =
!!changeRecord?.base?.cherry_pick_of_change &&
@@ -412,7 +413,7 @@
_computePushCertificateValidation(
serverConfig?: ServerInfo,
change?: ParsedChangeInfo
- ): PushCertifacteValidationInfo | undefined {
+ ): PushCertificateValidationInfo | undefined {
if (!change || !serverConfig?.receive?.enable_signed_push) return undefined;
const rev = change.revisions[change.current_revision];
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js
deleted file mode 100644
index 3ed7dd4..0000000
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js
+++ /dev/null
@@ -1,782 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import '../../core/gr-router/gr-router.js';
-import './gr-change-metadata.js';
-import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
-
-const basicFixture = fixtureFromElement('gr-change-metadata');
-
-const pluginApi = _testOnly_initGerritPluginApi();
-
-suite('gr-change-metadata tests', () => {
- let element;
-
- setup(() => {
- stub('gr-rest-api-interface', {
- getConfig() { return Promise.resolve({}); },
- getLoggedIn() { return Promise.resolve(false); },
- });
-
- element = basicFixture.instantiate();
- });
-
- test('computed fields', () => {
- assert.isFalse(element._computeHideStrategy({status: 'NEW'}));
- assert.isTrue(element._computeHideStrategy({status: 'MERGED'}));
- assert.isTrue(element._computeHideStrategy({status: 'ABANDONED'}));
- assert.equal(element._computeStrategy({submit_type: 'CHERRY_PICK'}),
- 'Cherry Pick');
- assert.equal(element._computeStrategy({submit_type: 'REBASE_ALWAYS'}),
- 'Rebase Always');
- });
-
- test('computed fields requirements', () => {
- assert.isFalse(element._computeShowRequirements({status: 'MERGED'}));
- assert.isFalse(element._computeShowRequirements({status: 'ABANDONED'}));
-
- // No labels and no requirements: submit status is useless
- assert.isFalse(element._computeShowRequirements({
- status: 'NEW',
- labels: {},
- }));
-
- // Work in Progress: submit status should be present
- assert.isTrue(element._computeShowRequirements({
- status: 'NEW',
- labels: {},
- work_in_progress: true,
- }));
-
- // We have at least one reason to display Submit Status
- assert.isTrue(element._computeShowRequirements({
- status: 'NEW',
- labels: {
- Verified: {
- approved: false,
- },
- },
- requirements: [],
- }));
- assert.isTrue(element._computeShowRequirements({
- status: 'NEW',
- labels: {},
- requirements: [{
- fallback_text: 'Resolve all comments',
- status: 'OK',
- }],
- }));
- });
-
- test('show strategy for open change', () => {
- element.change = {status: 'NEW', submit_type: 'CHERRY_PICK', labels: {}};
- flush();
- const strategy = element.shadowRoot
- .querySelector('.strategy');
- assert.ok(strategy);
- assert.isFalse(strategy.hasAttribute('hidden'));
- assert.equal(strategy.children[1].innerHTML, 'Cherry Pick');
- });
-
- test('hide strategy for closed change', () => {
- element.change = {status: 'MERGED', labels: {}};
- flush();
- assert.isTrue(element.shadowRoot
- .querySelector('.strategy').hasAttribute('hidden'));
- });
-
- test('weblinks use GerritNav interface', () => {
- const weblinksStub = sinon.stub(GerritNav, '_generateWeblinks')
- .returns([{name: 'stubb', url: '#s'}]);
- element.commitInfo = {};
- element.serverConfig = {};
- flush();
- const webLinks = element.$.webLinks;
- assert.isTrue(weblinksStub.called);
- assert.isFalse(webLinks.hasAttribute('hidden'));
- assert.equal(element._computeWebLinks(element.commitInfo).length, 1);
- });
-
- test('weblinks hidden when no weblinks', () => {
- element.commitInfo = {};
- element.serverConfig = {};
- flush();
- const webLinks = element.$.webLinks;
- assert.isTrue(webLinks.hasAttribute('hidden'));
- });
-
- test('weblinks hidden when only gitiles weblink', () => {
- element.commitInfo = {web_links: [{name: 'gitiles', url: '#'}]};
- element.serverConfig = {};
- flush();
- const webLinks = element.$.webLinks;
- assert.isTrue(webLinks.hasAttribute('hidden'));
- assert.equal(element._computeWebLinks(element.commitInfo), null);
- });
-
- test('weblinks hidden when sole weblink is set as primary', () => {
- const browser = 'browser';
- element.commitInfo = {web_links: [{name: browser, url: '#'}]};
- element.serverConfig = {
- gerrit: {
- primary_weblink_name: browser,
- },
- };
- flush();
- const webLinks = element.$.webLinks;
- assert.isTrue(webLinks.hasAttribute('hidden'));
- });
-
- test('weblinks are visible when other weblinks', () => {
- const router = document.createElement('gr-router');
- sinon.stub(GerritNav, '_generateWeblinks').callsFake(
- router._generateWeblinks.bind(router));
-
- element.commitInfo = {web_links: [{name: 'test', url: '#'}]};
- flush();
- const webLinks = element.$.webLinks;
- assert.isFalse(webLinks.hasAttribute('hidden'));
- assert.equal(element._computeWebLinks(element.commitInfo).length, 1);
- // With two non-gitiles weblinks, there are two returned.
- element.commitInfo = {
- web_links: [{name: 'test', url: '#'}, {name: 'test2', url: '#'}]};
- assert.equal(element._computeWebLinks(element.commitInfo).length, 2);
- });
-
- test('weblinks are visible when gitiles and other weblinks', () => {
- const router = document.createElement('gr-router');
- sinon.stub(GerritNav, '_generateWeblinks').callsFake(
- router._generateWeblinks.bind(router));
-
- element.commitInfo = {
- web_links: [{name: 'test', url: '#'}, {name: 'gitiles', url: '#'}]};
- flush();
- const webLinks = element.$.webLinks;
- assert.isFalse(webLinks.hasAttribute('hidden'));
- // Only the non-gitiles weblink is returned.
- assert.equal(element._computeWebLinks(element.commitInfo).length, 1);
- });
-
- suite('_getNonOwnerRole', () => {
- let change;
-
- setup(() => {
- change = {
- owner: {
- email: 'abc@def',
- _account_id: 1019328,
- },
- revisions: {
- rev1: {
- _number: 1,
- uploader: {
- email: 'ghi@def',
- _account_id: 1011123,
- },
- commit: {
- author: {email: 'jkl@def'},
- committer: {email: 'ghi@def'},
- },
- },
- },
- current_revision: 'rev1',
- };
- });
-
- suite('role=uploader', () => {
- test('_getNonOwnerRole for uploader', () => {
- assert.deepEqual(
- element._getNonOwnerRole(change, element._CHANGE_ROLE.UPLOADER),
- {email: 'ghi@def', _account_id: 1011123});
- });
-
- 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;
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.UPLOADER));
- });
-
- test('_getNonOwnerRole null for uploader with no current rev', () => {
- delete change.current_revision;
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.UPLOADER));
- });
-
- test('_computeShowRoleClass show uploader', () => {
- assert.equal(element._computeShowRoleClass(
- change, element._CHANGE_ROLE.UPLOADER), '');
- });
-
- test('_computeShowRoleClass hide uploader', () => {
- // Set the uploader email to be the same as the owner.
- change.revisions.rev1.uploader._account_id = 1019328;
- assert.equal(element._computeShowRoleClass(change,
- element._CHANGE_ROLE.UPLOADER), 'hideDisplay');
- });
- });
-
- suite('role=committer', () => {
- test('_getNonOwnerRole for committer', () => {
- change.revisions.rev1.uploader.email = 'ghh@def';
- assert.deepEqual(
- element._getNonOwnerRole(change, element._CHANGE_ROLE.COMMITTER),
- {email: 'ghi@def'});
- });
-
- test('_getNonOwnerRole is null if committer is same as uploader', () => {
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.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';
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.COMMITTER));
- });
-
- test('_getNonOwnerRole null for committer with no current rev', () => {
- delete change.current_revision;
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.COMMITTER));
- });
-
- test('_getNonOwnerRole null for committer with no commit', () => {
- delete change.revisions.rev1.commit;
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.COMMITTER));
- });
-
- test('_getNonOwnerRole null for committer with no committer', () => {
- delete change.revisions.rev1.commit.committer;
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.COMMITTER));
- });
- });
-
- suite('role=author', () => {
- test('_getNonOwnerRole for author', () => {
- assert.deepEqual(
- element._getNonOwnerRole(change, element._CHANGE_ROLE.AUTHOR),
- {email: 'jkl@def'});
- });
-
- 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';
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.AUTHOR));
- });
-
- test('_getNonOwnerRole null for author with no current rev', () => {
- delete change.current_revision;
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.AUTHOR));
- });
-
- test('_getNonOwnerRole null for author with no commit', () => {
- delete change.revisions.rev1.commit;
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.AUTHOR));
- });
-
- test('_getNonOwnerRole null for author with no author', () => {
- delete change.revisions.rev1.commit.author;
- assert.isNotOk(element._getNonOwnerRole(change,
- element._CHANGE_ROLE.AUTHOR));
- });
- });
- });
-
- test('Push Certificate Validation test BAD', () => {
- const serverConfig = {
- receive: {
- enable_signed_push: true,
- },
- };
- const change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- owner: {
- _account_id: 1019328,
- },
- revisions: {
- rev1: {
- _number: 1,
- push_certificate: {
- key: {
- status: 'BAD',
- problems: [
- 'No public keys found for key ID E5E20E52',
- ],
- },
- },
- },
- },
- current_revision: 'rev1',
- status: 'NEW',
- labels: {},
- mergeable: true,
- };
- const result =
- element._computePushCertificateValidation(serverConfig, change);
- assert.equal(result.message,
- 'Push certificate is invalid:\n' +
- 'No public keys found for key ID E5E20E52');
- assert.equal(result.icon, 'gr-icons:close');
- assert.equal(result.class, 'invalid');
- });
-
- test('Push Certificate Validation test TRUSTED', () => {
- const serverConfig = {
- receive: {
- enable_signed_push: true,
- },
- };
- const change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- owner: {
- _account_id: 1019328,
- },
- revisions: {
- rev1: {
- _number: 1,
- push_certificate: {
- key: {
- status: 'TRUSTED',
- },
- },
- },
- },
- current_revision: 'rev1',
- status: 'NEW',
- labels: {},
- mergeable: true,
- };
- const result =
- element._computePushCertificateValidation(serverConfig, change);
- assert.equal(result.message,
- 'Push certificate is valid and key is trusted');
- assert.equal(result.icon, 'gr-icons:check');
- assert.equal(result.class, 'trusted');
- });
-
- test('Push Certificate Validation is missing test', () => {
- const serverConfig = {
- receive: {
- enable_signed_push: true,
- },
- };
- const change = {
- change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
- owner: {
- _account_id: 1019328,
- },
- revisions: {
- rev1: {
- _number: 1,
- },
- },
- current_revision: 'rev1',
- status: 'NEW',
- labels: {},
- mergeable: true,
- };
- const result =
- element._computePushCertificateValidation(serverConfig, change);
- assert.equal(result.message,
- 'This patch set was created without a push certificate');
- assert.equal(result.icon, 'gr-icons:help');
- assert.equal(result.class, 'help');
- });
-
- test('_computeParents', () => {
- const parents = [{commit: '123', subject: 'abc'}];
- const revision = {commit: {parents}};
- assert.deepEqual(element._computeParents({}, {}), []);
- assert.equal(element._computeParents(null, revision), parents);
- const change = current_revision => {
- return {current_revision, revisions: {456: revision}};
- };
- assert.deepEqual(element._computeParents(change(null), null), []);
- const change_bad_revision = change('789');
- assert.deepEqual(element._computeParents(change_bad_revision, {}), []);
- const change_no_commit = {current_revision: '456', revisions: {456: {}}};
- assert.deepEqual(element._computeParents(change_no_commit, null), []);
- const change_good = change('456');
- assert.equal(element._computeParents(change_good, null), parents);
- });
-
- test('_currentParents', () => {
- const revision = parent => {
- return {commit: {parents: [{commit: parent, subject: 'abc'}]}};
- };
- element.change = {
- current_revision: '456',
- revisions: {456: revision('111')},
- owner: {},
- };
- element.revision = revision('222');
- assert.equal(element._currentParents[0].commit, '222');
- element.revision = revision('333');
- assert.equal(element._currentParents[0].commit, '333');
- element.revision = null;
- assert.equal(element._currentParents[0].commit, '111');
- element.change = {current_revision: null};
- assert.deepEqual(element._currentParents, []);
- });
-
- test('_computeParentsLabel', () => {
- const parent = {commit: 'abc123', subject: 'My parent commit'};
- assert.equal(element._computeParentsLabel([parent]), 'Parent');
- assert.equal(element._computeParentsLabel([parent, parent]),
- 'Parents');
- });
-
- test('_computeParentListClass', () => {
- const parent = {commit: 'abc123', subject: 'My parent commit'};
- assert.equal(element._computeParentListClass([parent], true),
- 'parentList nonMerge current');
- assert.equal(element._computeParentListClass([parent], false),
- 'parentList nonMerge notCurrent');
- assert.equal(element._computeParentListClass([parent, parent], false),
- 'parentList merge notCurrent');
- assert.equal(element._computeParentListClass([parent, parent], true),
- 'parentList merge current');
- });
-
- test('_showAddTopic', () => {
- assert.isTrue(element._showAddTopic(null, false));
- assert.isTrue(element._showAddTopic({base: {topic: null}}, false));
- assert.isFalse(element._showAddTopic({base: {topic: null}}, true));
- assert.isFalse(element._showAddTopic({base: {topic: 'foo'}}, true));
- assert.isFalse(element._showAddTopic({base: {topic: 'foo'}}, false));
- });
-
- test('_showTopicChip', () => {
- assert.isFalse(element._showTopicChip(null, false));
- assert.isFalse(element._showTopicChip({base: {topic: null}}, false));
- assert.isFalse(element._showTopicChip({base: {topic: null}}, true));
- assert.isFalse(element._showTopicChip({base: {topic: 'foo'}}, true));
- assert.isTrue(element._showTopicChip({base: {topic: 'foo'}}, false));
- });
-
- test('_showCherryPickOf', () => {
- assert.isFalse(element._showCherryPickOf(null));
- assert.isFalse(element._showCherryPickOf({
- base: {
- cherry_pick_of_change: null,
- cherry_pick_of_patch_set: null,
- },
- }));
- assert.isTrue(element._showCherryPickOf({
- base: {
- cherry_pick_of_change: 123,
- cherry_pick_of_patch_set: 1,
- },
- }));
- });
-
- suite('Topic removal', () => {
- let change;
- setup(() => {
- change = {
- _number: 'the number',
- actions: {
- topic: {enabled: false},
- },
- change_id: 'the id',
- topic: 'the topic',
- status: 'NEW',
- submit_type: 'CHERRY_PICK',
- labels: {
- test: {
- all: [{_account_id: 1, name: 'bojack', value: 1}],
- default_value: 0,
- values: [],
- },
- },
- removable_reviewers: [],
- };
- });
-
- test('_computeTopicReadOnly', () => {
- let mutable = false;
- assert.isTrue(element._computeTopicReadOnly(mutable, change));
- mutable = true;
- assert.isTrue(element._computeTopicReadOnly(mutable, change));
- change.actions.topic.enabled = true;
- assert.isFalse(element._computeTopicReadOnly(mutable, change));
- mutable = false;
- assert.isTrue(element._computeTopicReadOnly(mutable, change));
- });
-
- test('topic read only hides delete button', () => {
- element.account = {};
- element.change = change;
- flush();
- const button = element.shadowRoot
- .querySelector('gr-linked-chip').shadowRoot
- .querySelector('gr-button');
- assert.isTrue(button.hasAttribute('hidden'));
- });
-
- test('topic not read only does not hide delete button', () => {
- element.account = {test: true};
- change.actions.topic.enabled = true;
- element.change = change;
- flush();
- const button = element.shadowRoot
- .querySelector('gr-linked-chip').shadowRoot
- .querySelector('gr-button');
- assert.isFalse(button.hasAttribute('hidden'));
- });
- });
-
- suite('Hashtag removal', () => {
- let change;
- setup(() => {
- change = {
- _number: 'the number',
- actions: {
- hashtags: {enabled: false},
- },
- change_id: 'the id',
- hashtags: ['test-hashtag'],
- status: 'NEW',
- submit_type: 'CHERRY_PICK',
- labels: {
- test: {
- all: [{_account_id: 1, name: 'bojack', value: 1}],
- default_value: 0,
- values: [],
- },
- },
- removable_reviewers: [],
- };
- });
-
- test('_computeHashtagReadOnly', () => {
- flush();
- let mutable = false;
- assert.isTrue(element._computeHashtagReadOnly(mutable, change));
- mutable = true;
- assert.isTrue(element._computeHashtagReadOnly(mutable, change));
- change.actions.hashtags.enabled = true;
- assert.isFalse(element._computeHashtagReadOnly(mutable, change));
- mutable = false;
- assert.isTrue(element._computeHashtagReadOnly(mutable, change));
- });
-
- test('hashtag read only hides delete button', () => {
- flush();
- element.account = {};
- element.change = change;
- flush();
- const button = element.shadowRoot
- .querySelector('gr-linked-chip').shadowRoot
- .querySelector('gr-button');
- assert.isTrue(button.hasAttribute('hidden'));
- });
-
- test('hashtag not read only does not hide delete button', () => {
- flush();
- element.account = {test: true};
- change.actions.hashtags.enabled = true;
- element.change = change;
- flush();
- const button = element.shadowRoot
- .querySelector('gr-linked-chip').shadowRoot
- .querySelector('gr-button');
- assert.isFalse(button.hasAttribute('hidden'));
- });
- });
-
- suite('remove reviewer votes', () => {
- setup(() => {
- sinon.stub(element, '_computeTopicReadOnly').returns(true);
- element.change = {
- _number: 42,
- change_id: 'the id',
- actions: [],
- topic: 'the topic',
- status: 'NEW',
- submit_type: 'CHERRY_PICK',
- labels: {
- test: {
- all: [{_account_id: 1, name: 'bojack', value: 1}],
- default_value: 0,
- values: [],
- },
- },
- removable_reviewers: [],
- };
- flush();
- });
-
- suite('assignee field', () => {
- const dummyAccount = {
- _account_id: 1,
- name: 'bojack',
- };
- const change = {
- actions: {
- assignee: {enabled: false},
- },
- assignee: dummyAccount,
- };
- let deleteStub;
- let setStub;
-
- setup(() => {
- deleteStub = sinon.stub(element.$.restAPI, 'deleteAssignee');
- setStub = sinon.stub(element.$.restAPI, 'setAssignee');
- element.serverConfig = {
- change: {
- enable_assignee: true,
- },
- };
- });
-
- test('changing change recomputes _assignee', () => {
- assert.isFalse(!!element._assignee.length);
- const change = element.change;
- change.assignee = dummyAccount;
- element._changeChanged(change);
- assert.deepEqual(element._assignee[0], dummyAccount);
- });
-
- test('modifying _assignee calls API', () => {
- assert.isFalse(!!element._assignee.length);
- element.set('_assignee', [dummyAccount]);
- assert.isTrue(setStub.calledOnce);
- assert.deepEqual(element.change.assignee, dummyAccount);
- element.set('_assignee', [dummyAccount]);
- assert.isTrue(setStub.calledOnce);
- element.set('_assignee', []);
- assert.isTrue(deleteStub.calledOnce);
- assert.equal(element.change.assignee, undefined);
- element.set('_assignee', []);
- assert.isTrue(deleteStub.calledOnce);
- });
-
- test('_computeAssigneeReadOnly', () => {
- let mutable = false;
- assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
- mutable = true;
- assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
- change.actions.assignee.enabled = true;
- assert.isFalse(element._computeAssigneeReadOnly(mutable, change));
- mutable = false;
- assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
- });
- });
-
- test('changing topic', () => {
- const newTopic = 'the new topic';
- sinon.stub(element.$.restAPI, 'setChangeTopic').returns(
- Promise.resolve(newTopic));
- element._handleTopicChanged({detail: newTopic});
- const topicChangedSpy = sinon.spy();
- element.addEventListener('topic-changed', topicChangedSpy);
- assert.isTrue(element.$.restAPI.setChangeTopic.calledWith(
- 42, newTopic));
- return element.$.restAPI.setChangeTopic.lastCall.returnValue
- .then(() => {
- assert.equal(element.change.topic, newTopic);
- assert.isTrue(topicChangedSpy.called);
- });
- });
-
- test('topic removal', () => {
- sinon.stub(element.$.restAPI, 'setChangeTopic').returns(
- Promise.resolve());
- const chip = element.shadowRoot
- .querySelector('gr-linked-chip');
- const remove = chip.$.remove;
- const topicChangedSpy = sinon.spy();
- element.addEventListener('topic-changed', topicChangedSpy);
- MockInteractions.tap(remove);
- assert.isTrue(chip.disabled);
- assert.isTrue(element.$.restAPI.setChangeTopic.calledWith(
- 42));
- return element.$.restAPI.setChangeTopic.lastCall.returnValue
- .then(() => {
- assert.isFalse(chip.disabled);
- assert.equal(element.change.topic, '');
- assert.isTrue(topicChangedSpy.called);
- });
- });
-
- test('changing hashtag', () => {
- flush();
- element._newHashtag = 'new hashtag';
- const newHashtag = ['new hashtag'];
- sinon.stub(element.$.restAPI, 'setChangeHashtag').returns(
- Promise.resolve(newHashtag));
- element._handleHashtagChanged({}, 'new hashtag');
- assert.isTrue(element.$.restAPI.setChangeHashtag.calledWith(
- 42, {add: ['new hashtag']}));
- return element.$.restAPI.setChangeHashtag.lastCall.returnValue
- .then(() => {
- assert.equal(element.change.hashtags, newHashtag);
- });
- });
- });
-
- test('editTopic', () => {
- element.account = {test: true};
- element.change = {actions: {topic: {enabled: true}}};
- flush();
-
- const label = element.shadowRoot
- .querySelector('.topicEditableLabel');
- assert.ok(label);
- sinon.stub(label, 'open');
- element.editTopic();
- flush();
-
- assert.isTrue(label.open.called);
- });
-
- suite('plugin endpoints', () => {
- test('endpoint params', done => {
- element.change = {labels: {}};
- element.revision = {};
- let hookEl;
- let plugin;
- pluginApi.install(
- p => {
- plugin = p;
- plugin.hook('change-metadata-item').getLastAttached()
- .then(el => hookEl = el);
- },
- '0.1',
- 'http://some/plugins/url.html');
- getPluginLoader().loadPlugins([]);
- flush(() => {
- assert.strictEqual(hookEl.plugin, plugin);
- assert.strictEqual(hookEl.change, element.change);
- assert.strictEqual(hookEl.revision, element.revision);
- done();
- });
- });
- });
-});
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts
new file mode 100644
index 0000000..e95e3ba
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts
@@ -0,0 +1,992 @@
+/**
+ * @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 '../../../test/common-test-setup-karma';
+import '../../core/gr-router/gr-router';
+import './gr-change-metadata';
+import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
+import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit';
+import {GrChangeMetadata} from './gr-change-metadata';
+import {
+ createServerInfo,
+ createUserConfig,
+ createParsedChange,
+ createAccountWithId,
+ createRequirement,
+ createCommitInfoWithRequiredCommit,
+ createWebLinkInfo,
+ createGerritInfo,
+ createGitPerson,
+ createCommit,
+ createRevision,
+ createAccountDetailWithId,
+ createChangeConfig,
+} from '../../../test/test-data-generators';
+import {
+ ChangeStatus,
+ SubmitType,
+ RequirementStatus,
+ GpgKeyInfoStatus,
+} from '../../../constants/constants';
+import {ParsedChangeInfo} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
+import {
+ EmailAddress,
+ AccountId,
+ CommitId,
+ ServerInfo,
+ RevisionInfo,
+ ParentCommitInfo,
+ TopicName,
+ ElementPropertyDeepChange,
+ PatchSetNum,
+ NumericChangeId,
+ LabelValueToDescriptionMap,
+ Hashtag,
+} from '../../../types/common';
+import {SinonStubbedMember} from 'sinon/pkg/sinon-esm';
+import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
+import {tap} from '@polymer/iron-test-helpers/mock-interactions';
+import {GrEditableLabel} from '../../shared/gr-editable-label/gr-editable-label';
+import {PluginApi} from '../../plugins/gr-plugin-types';
+import {GrEndpointDecorator} from '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+
+const basicFixture = fixtureFromElement('gr-change-metadata');
+
+const pluginApi = _testOnly_initGerritPluginApi();
+
+suite('gr-change-metadata tests', () => {
+ let element: GrChangeMetadata;
+
+ setup(() => {
+ stub('gr-rest-api-interface', {
+ getConfig() {
+ return Promise.resolve({
+ ...createServerInfo(),
+ user: {
+ ...createUserConfig(),
+ anonymous_coward_name: 'test coward name',
+ },
+ });
+ },
+ getLoggedIn() {
+ return Promise.resolve(false);
+ },
+ });
+
+ element = basicFixture.instantiate();
+ });
+
+ test('computed fields', () => {
+ assert.isFalse(
+ element._computeHideStrategy({
+ ...createParsedChange(),
+ status: ChangeStatus.NEW,
+ })
+ );
+ assert.isTrue(
+ element._computeHideStrategy({
+ ...createParsedChange(),
+ status: ChangeStatus.MERGED,
+ })
+ );
+ assert.isTrue(
+ element._computeHideStrategy({
+ ...createParsedChange(),
+ status: ChangeStatus.ABANDONED,
+ })
+ );
+ assert.equal(
+ element._computeStrategy({
+ ...createParsedChange(),
+ submit_type: SubmitType.CHERRY_PICK,
+ }),
+ 'Cherry Pick'
+ );
+ assert.equal(
+ element._computeStrategy({
+ ...createParsedChange(),
+ submit_type: SubmitType.REBASE_ALWAYS,
+ }),
+ 'Rebase Always'
+ );
+ });
+
+ test('computed fields requirements', () => {
+ assert.isFalse(
+ element._computeShowRequirements({
+ ...createParsedChange(),
+ status: ChangeStatus.MERGED,
+ })
+ );
+ assert.isFalse(
+ element._computeShowRequirements({
+ ...createParsedChange(),
+ status: ChangeStatus.ABANDONED,
+ })
+ );
+
+ // No labels and no requirements: submit status is useless
+ assert.isFalse(
+ element._computeShowRequirements({
+ ...createParsedChange(),
+ status: ChangeStatus.NEW,
+ labels: {},
+ })
+ );
+
+ // Work in Progress: submit status should be present
+ assert.isTrue(
+ element._computeShowRequirements({
+ ...createParsedChange(),
+ status: ChangeStatus.NEW,
+ labels: {},
+ work_in_progress: true,
+ })
+ );
+
+ // We have at least one reason to display Submit Status
+ assert.isTrue(
+ element._computeShowRequirements({
+ ...createParsedChange(),
+ status: ChangeStatus.NEW,
+ labels: {
+ Verified: {
+ approved: createAccountWithId(),
+ },
+ },
+ requirements: [],
+ })
+ );
+ assert.isTrue(
+ element._computeShowRequirements({
+ ...createParsedChange(),
+ status: ChangeStatus.NEW,
+ labels: {},
+ requirements: [
+ {
+ ...createRequirement(),
+ fallbackText: 'Resolve all comments',
+ status: RequirementStatus.OK,
+ },
+ ],
+ })
+ );
+ });
+
+ test('show strategy for open change', () => {
+ element.change = {
+ ...createParsedChange(),
+ status: ChangeStatus.NEW,
+ submit_type: SubmitType.CHERRY_PICK,
+ labels: {},
+ };
+ flush();
+ const strategy = element.shadowRoot?.querySelector('.strategy');
+ assert.ok(strategy);
+ assert.isFalse(strategy?.hasAttribute('hidden'));
+ assert.equal(strategy?.children[1].innerHTML, 'Cherry Pick');
+ });
+
+ test('hide strategy for closed change', () => {
+ element.change = {
+ ...createParsedChange(),
+ status: ChangeStatus.MERGED,
+ labels: {},
+ };
+ flush();
+ assert.isTrue(
+ element.shadowRoot?.querySelector('.strategy')?.hasAttribute('hidden')
+ );
+ });
+
+ test('weblinks use GerritNav interface', () => {
+ const weblinksStub = sinon
+ .stub(GerritNav, '_generateWeblinks')
+ .returns([{name: 'stubb', url: '#s'}]);
+ element.commitInfo = createCommitInfoWithRequiredCommit();
+ element.serverConfig = createServerInfo();
+ flush();
+ const webLinks = element.$.webLinks;
+ assert.isTrue(weblinksStub.called);
+ assert.isFalse(webLinks.hasAttribute('hidden'));
+ assert.equal(element._computeWebLinks(element.commitInfo)?.length, 1);
+ });
+
+ test('weblinks hidden when no weblinks', () => {
+ element.commitInfo = createCommitInfoWithRequiredCommit();
+ element.serverConfig = createServerInfo();
+ flush();
+ const webLinks = element.$.webLinks;
+ assert.isTrue(webLinks.hasAttribute('hidden'));
+ });
+
+ test('weblinks hidden when only gitiles weblink', () => {
+ element.commitInfo = {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [{...createWebLinkInfo(), name: 'gitiles', url: '#'}],
+ };
+ element.serverConfig = createServerInfo();
+ flush();
+ const webLinks = element.$.webLinks;
+ assert.isTrue(webLinks.hasAttribute('hidden'));
+ assert.equal(element._computeWebLinks(element.commitInfo), null);
+ });
+
+ test('weblinks hidden when sole weblink is set as primary', () => {
+ const browser = 'browser';
+ element.commitInfo = {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [{...createWebLinkInfo(), name: browser, url: '#'}],
+ };
+ element.serverConfig = {
+ ...createServerInfo(),
+ gerrit: {
+ ...createGerritInfo(),
+ primary_weblink_name: browser,
+ },
+ };
+ flush();
+ const webLinks = element.$.webLinks;
+ assert.isTrue(webLinks.hasAttribute('hidden'));
+ });
+
+ test('weblinks are visible when other weblinks', () => {
+ const router = document.createElement('gr-router');
+ sinon
+ .stub(GerritNav, '_generateWeblinks')
+ .callsFake(router._generateWeblinks.bind(router));
+
+ element.commitInfo = {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [{...createWebLinkInfo(), name: 'test', url: '#'}],
+ };
+ flush();
+ const webLinks = element.$.webLinks;
+ assert.isFalse(webLinks.hasAttribute('hidden'));
+ assert.equal(element._computeWebLinks(element.commitInfo)?.length, 1);
+ // With two non-gitiles weblinks, there are two returned.
+ element.commitInfo = {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [
+ {...createWebLinkInfo(), name: 'test', url: '#'},
+ {...createWebLinkInfo(), name: 'test2', url: '#'},
+ ],
+ };
+ assert.equal(element._computeWebLinks(element.commitInfo)?.length, 2);
+ });
+
+ test('weblinks are visible when gitiles and other weblinks', () => {
+ const router = document.createElement('gr-router');
+ sinon
+ .stub(GerritNav, '_generateWeblinks')
+ .callsFake(router._generateWeblinks.bind(router));
+
+ element.commitInfo = {
+ ...createCommitInfoWithRequiredCommit(),
+ web_links: [
+ {...createWebLinkInfo(), name: 'test', url: '#'},
+ {...createWebLinkInfo(), name: 'gitiles', url: '#'},
+ ],
+ };
+ flush();
+ const webLinks = element.$.webLinks;
+ assert.isFalse(webLinks.hasAttribute('hidden'));
+ // Only the non-gitiles weblink is returned.
+ assert.equal(element._computeWebLinks(element.commitInfo)?.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'},
+ committer: {...createGitPerson(), email: 'ghi@def'},
+ },
+ },
+ },
+ current_revision: 'rev1' as CommitId,
+ };
+ });
+
+ suite('role=uploader', () => {
+ test('_getNonOwnerRole for uploader', () => {
+ assert.deepEqual(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.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;
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.UPLOADER)
+ );
+ });
+
+ test('_getNonOwnerRole null for uploader with no current rev', () => {
+ delete change!.current_revision;
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.UPLOADER)
+ );
+ });
+
+ test('_computeShowRoleClass show uploader', () => {
+ assert.equal(
+ element._computeShowRoleClass(change, element._CHANGE_ROLE.UPLOADER),
+ ''
+ );
+ });
+
+ test('_computeShowRoleClass hide uploader', () => {
+ // Set the uploader email to be the same as the owner.
+ change!.revisions.rev1.uploader!._account_id = 1019328 as AccountId;
+ assert.equal(
+ element._computeShowRoleClass(change, element._CHANGE_ROLE.UPLOADER),
+ 'hideDisplay'
+ );
+ });
+ });
+
+ suite('role=committer', () => {
+ test('_getNonOwnerRole for committer', () => {
+ change!.revisions.rev1.uploader!.email = 'ghh@def' as EmailAddress;
+ assert.deepEqual(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.COMMITTER),
+ {...createGitPerson(), email: 'ghi@def'}
+ );
+ });
+
+ test('_getNonOwnerRole is null if committer is same as uploader', () => {
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.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';
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.COMMITTER)
+ );
+ });
+
+ test('_getNonOwnerRole null for committer with no current rev', () => {
+ delete change!.current_revision;
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.COMMITTER)
+ );
+ });
+
+ test('_getNonOwnerRole null for committer with no commit', () => {
+ delete change!.revisions.rev1.commit;
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.COMMITTER)
+ );
+ });
+
+ test('_getNonOwnerRole null for committer with no committer', () => {
+ delete change!.revisions.rev1.commit!.committer;
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.COMMITTER)
+ );
+ });
+ });
+
+ suite('role=author', () => {
+ test('_getNonOwnerRole for author', () => {
+ assert.deepEqual(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.AUTHOR),
+ {...createGitPerson(), email: 'jkl@def'}
+ );
+ });
+
+ 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';
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.AUTHOR)
+ );
+ });
+
+ test('_getNonOwnerRole null for author with no current rev', () => {
+ delete change!.current_revision;
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.AUTHOR)
+ );
+ });
+
+ test('_getNonOwnerRole null for author with no commit', () => {
+ delete change!.revisions.rev1.commit;
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.AUTHOR)
+ );
+ });
+
+ test('_getNonOwnerRole null for author with no author', () => {
+ delete change!.revisions.rev1.commit!.author;
+ assert.isNotOk(
+ element._getNonOwnerRole(change, element._CHANGE_ROLE.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,
+ };
+ });
+
+ 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'],
+ },
+ };
+ const result = element._computePushCertificateValidation(
+ serverConfig,
+ change
+ );
+ assert.equal(
+ result?.message,
+ 'Push certificate is invalid:\n' +
+ 'No public keys found for key ID E5E20E52'
+ );
+ assert.equal(result?.icon, 'gr-icons:close');
+ assert.equal(result?.class, 'invalid');
+ });
+
+ test('Push Certificate Validation test TRUSTED', () => {
+ change!.revisions.rev1!.push_certificate = {
+ certificate: 'Push certificate',
+ key: {
+ status: GpgKeyInfoStatus.TRUSTED,
+ },
+ };
+ const result = element._computePushCertificateValidation(
+ serverConfig,
+ change
+ );
+ assert.equal(
+ result?.message,
+ 'Push certificate is valid and key is trusted'
+ );
+ assert.equal(result?.icon, 'gr-icons:check');
+ assert.equal(result?.class, 'trusted');
+ });
+
+ test('Push Certificate Validation is missing test', () => {
+ change!.revisions.rev1! = createRevision(1);
+ const result = element._computePushCertificateValidation(
+ serverConfig,
+ change
+ );
+ assert.equal(
+ result?.message,
+ 'This patch set was created without a push certificate'
+ );
+ assert.equal(result?.icon, 'gr-icons:help');
+ assert.equal(result?.class, 'help');
+ });
+ });
+
+ test('_computeParents', () => {
+ const parents: ParentCommitInfo[] = [
+ {...createCommit(), commit: '123' as CommitId, subject: 'abc'},
+ ];
+ const revision: RevisionInfo = {
+ ...createRevision(1),
+ commit: {...createCommit(), parents},
+ };
+ assert.equal(element._computeParents(undefined, revision), parents);
+ const change = (current_revision: CommitId): ParsedChangeInfo => {
+ return {
+ ...createParsedChange(),
+ current_revision,
+ revisions: {456: revision},
+ };
+ };
+ const change_bad_revision = change('789' as CommitId);
+ assert.deepEqual(
+ element._computeParents(change_bad_revision, createRevision()),
+ []
+ );
+ const change_no_commit: ParsedChangeInfo = {
+ ...createParsedChange(),
+ current_revision: '456' as CommitId,
+ revisions: {456: createRevision()},
+ };
+ assert.deepEqual(element._computeParents(change_no_commit, undefined), []);
+ const change_good = change('456' as CommitId);
+ assert.equal(element._computeParents(change_good, undefined), parents);
+ });
+
+ test('_currentParents', () => {
+ 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);
+ assert.equal(element._currentParents[0].commit, '222');
+ element.revision = revision('333' as CommitId);
+ assert.equal(element._currentParents[0].commit, '333');
+ element.revision = undefined;
+ assert.equal(element._currentParents[0].commit, '111');
+ element.change = createParsedChange();
+ delete element.change.current_revision;
+ assert.deepEqual(element._currentParents, []);
+ });
+
+ test('_computeParentsLabel', () => {
+ const parent: ParentCommitInfo = {
+ ...createCommit(),
+ commit: 'abc123' as CommitId,
+ subject: 'My parent commit',
+ };
+ assert.equal(element._computeParentsLabel([parent]), 'Parent');
+ assert.equal(element._computeParentsLabel([parent, parent]), 'Parents');
+ });
+
+ test('_computeParentListClass', () => {
+ const parent: ParentCommitInfo = {
+ ...createCommit(),
+ commit: 'abc123' as CommitId,
+ subject: 'My parent commit',
+ };
+ assert.equal(
+ element._computeParentListClass([parent], true),
+ 'parentList nonMerge current'
+ );
+ assert.equal(
+ element._computeParentListClass([parent], false),
+ 'parentList nonMerge notCurrent'
+ );
+ assert.equal(
+ element._computeParentListClass([parent, parent], false),
+ 'parentList merge notCurrent'
+ );
+ assert.equal(
+ element._computeParentListClass([parent, parent], true),
+ 'parentList merge current'
+ );
+ });
+
+ test('_showAddTopic', () => {
+ const changeRecord: ElementPropertyDeepChange<
+ GrChangeMetadata,
+ 'change'
+ > = {
+ base: {...createParsedChange()},
+ path: '',
+ value: undefined,
+ };
+ assert.isTrue(element._showAddTopic(undefined, false));
+ assert.isTrue(element._showAddTopic(changeRecord, false));
+ assert.isFalse(element._showAddTopic(changeRecord, true));
+ changeRecord.base!.topic = 'foo' as TopicName;
+ assert.isFalse(element._showAddTopic(changeRecord, true));
+ assert.isFalse(element._showAddTopic(changeRecord, false));
+ });
+
+ test('_showTopicChip', () => {
+ const changeRecord: ElementPropertyDeepChange<
+ GrChangeMetadata,
+ 'change'
+ > = {
+ base: {...createParsedChange()},
+ path: '',
+ value: undefined,
+ };
+ assert.isFalse(element._showTopicChip(undefined, false));
+ assert.isFalse(element._showTopicChip(changeRecord, false));
+ assert.isFalse(element._showTopicChip(changeRecord, true));
+ changeRecord.base!.topic = 'foo' as TopicName;
+ assert.isFalse(element._showTopicChip(changeRecord, true));
+ assert.isTrue(element._showTopicChip(changeRecord, false));
+ });
+
+ test('_showCherryPickOf', () => {
+ const changeRecord: ElementPropertyDeepChange<
+ GrChangeMetadata,
+ 'change'
+ > = {
+ base: {...createParsedChange()},
+ path: '',
+ value: undefined,
+ };
+ assert.isFalse(element._showCherryPickOf(undefined));
+ assert.isFalse(element._showCherryPickOf(changeRecord));
+ changeRecord.base!.cherry_pick_of_change = 123 as NumericChangeId;
+ changeRecord.base!.cherry_pick_of_patch_set = 1 as PatchSetNum;
+ assert.isTrue(element._showCherryPickOf(changeRecord));
+ });
+
+ 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;
+ assert.isTrue(element._computeTopicReadOnly(mutable, change));
+ mutable = true;
+ assert.isTrue(element._computeTopicReadOnly(mutable, change));
+ change!.actions!.topic!.enabled = true;
+ assert.isFalse(element._computeTopicReadOnly(mutable, change));
+ mutable = false;
+ assert.isTrue(element._computeTopicReadOnly(mutable, change));
+ });
+
+ test('topic read only hides delete button', () => {
+ element.account = createAccountDetailWithId();
+ element.change = change;
+ flush();
+ const button = element!
+ .shadowRoot!.querySelector('gr-linked-chip')!
+ .shadowRoot!.querySelector('gr-button');
+ assert.isTrue(button?.hasAttribute('hidden'));
+ });
+
+ test('topic not read only does not hide delete button', () => {
+ element.account = createAccountDetailWithId();
+ change.actions!.topic!.enabled = true;
+ element.change = change;
+ flush();
+ const button = element!
+ .shadowRoot!.querySelector('gr-linked-chip')!
+ .shadowRoot!.querySelector('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', () => {
+ flush();
+ let mutable = false;
+ assert.isTrue(element._computeHashtagReadOnly(mutable, change));
+ mutable = true;
+ assert.isTrue(element._computeHashtagReadOnly(mutable, change));
+ change!.actions!.hashtags!.enabled = true;
+ assert.isFalse(element._computeHashtagReadOnly(mutable, change));
+ mutable = false;
+ assert.isTrue(element._computeHashtagReadOnly(mutable, change));
+ });
+
+ test('hashtag read only hides delete button', () => {
+ flush();
+ element.account = createAccountDetailWithId();
+ element.change = change;
+ flush();
+ const button = element!
+ .shadowRoot!.querySelector('gr-linked-chip')!
+ .shadowRoot!.querySelector('gr-button');
+ assert.isTrue(button?.hasAttribute('hidden'));
+ });
+
+ test('hashtag not read only does not hide delete button', () => {
+ flush();
+ element.account = createAccountDetailWithId();
+ change!.actions!.hashtags!.enabled = true;
+ element.change = change;
+ flush();
+ const button = element!
+ .shadowRoot!.querySelector('gr-linked-chip')!
+ .shadowRoot!.querySelector('gr-button');
+ assert.isFalse(button?.hasAttribute('hidden'));
+ });
+ });
+
+ suite('remove reviewer votes', () => {
+ setup(() => {
+ 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: [],
+ };
+ flush();
+ });
+
+ suite('assignee field', () => {
+ const dummyAccount = createAccountWithId();
+ const change: ParsedChangeInfo = {
+ ...createParsedChange(),
+ actions: {
+ assignee: {enabled: false},
+ },
+ assignee: dummyAccount,
+ };
+ let deleteStub: SinonStubbedMember<RestApiService['deleteAssignee']>;
+ let setStub: SinonStubbedMember<RestApiService['setAssignee']>;
+
+ setup(() => {
+ deleteStub = sinon.stub(element.$.restAPI, 'deleteAssignee');
+ setStub = sinon.stub(element.$.restAPI, 'setAssignee');
+ element.serverConfig = {
+ ...createServerInfo(),
+ change: {
+ ...createChangeConfig(),
+ enable_assignee: true,
+ },
+ };
+ });
+
+ test('changing change recomputes _assignee', () => {
+ assert.isFalse(!!element._assignee?.length);
+ const change = element.change;
+ change!.assignee = dummyAccount;
+ element._changeChanged(change);
+ assert.deepEqual(element?._assignee?.[0], dummyAccount);
+ });
+
+ test('modifying _assignee calls API', () => {
+ assert.isFalse(!!element._assignee?.length);
+ element.set('_assignee', [dummyAccount]);
+ assert.isTrue(setStub.calledOnce);
+ assert.deepEqual(element.change!.assignee, dummyAccount);
+ element.set('_assignee', [dummyAccount]);
+ assert.isTrue(setStub.calledOnce);
+ element.set('_assignee', []);
+ assert.isTrue(deleteStub.calledOnce);
+ assert.equal(element.change!.assignee, undefined);
+ element.set('_assignee', []);
+ assert.isTrue(deleteStub.calledOnce);
+ });
+
+ test('_computeAssigneeReadOnly', () => {
+ let mutable = false;
+ assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
+ mutable = true;
+ assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
+ change.actions!.assignee!.enabled = true;
+ assert.isFalse(element._computeAssigneeReadOnly(mutable, change));
+ mutable = false;
+ assert.isTrue(element._computeAssigneeReadOnly(mutable, change));
+ });
+ });
+
+ test('changing topic', () => {
+ const newTopic = 'the new topic' as TopicName;
+ const setChangeTopicStub = sinon
+ .stub(element.$.restAPI, 'setChangeTopic')
+ .returns(Promise.resolve(newTopic));
+ element._handleTopicChanged(new CustomEvent('test', {detail: newTopic}));
+ const topicChangedSpy = sinon.spy();
+ element.addEventListener('topic-changed', topicChangedSpy);
+ assert.isTrue(
+ setChangeTopicStub.calledWith(42 as NumericChangeId, newTopic)
+ );
+ return setChangeTopicStub.lastCall.returnValue.then(() => {
+ assert.equal(element.change!.topic, newTopic);
+ assert.isTrue(topicChangedSpy.called);
+ });
+ });
+
+ test('topic removal', () => {
+ const newTopic = 'the new topic' as TopicName;
+ const setChangeTopicStub = sinon
+ .stub(element.$.restAPI, 'setChangeTopic')
+ .returns(Promise.resolve(newTopic));
+ const chip = element.shadowRoot!.querySelector('gr-linked-chip');
+ const remove = chip!.$.remove;
+ const topicChangedSpy = sinon.spy();
+ element.addEventListener('topic-changed', topicChangedSpy);
+ tap(remove);
+ assert.isTrue(chip?.disabled);
+ assert.isTrue(setChangeTopicStub.calledWith(42 as NumericChangeId));
+ return setChangeTopicStub.lastCall.returnValue.then(() => {
+ assert.isFalse(chip?.disabled);
+ assert.equal(element.change!.topic, '' as TopicName);
+ assert.isTrue(topicChangedSpy.called);
+ });
+ });
+
+ test('changing hashtag', () => {
+ flush();
+ element._newHashtag = 'new hashtag' as Hashtag;
+ const newHashtag: Hashtag[] = ['new hashtag' as Hashtag];
+ const setChangeHashtagStub = sinon
+ .stub(element.$.restAPI, 'setChangeHashtag')
+ .returns(Promise.resolve(newHashtag));
+ element._handleHashtagChanged();
+ assert.isTrue(
+ setChangeHashtagStub.calledWith(42 as NumericChangeId, {
+ add: ['new hashtag' as Hashtag],
+ })
+ );
+ return setChangeHashtagStub.lastCall.returnValue.then(() => {
+ assert.equal(element.change!.hashtags, newHashtag);
+ });
+ });
+ });
+
+ test('editTopic', () => {
+ element.account = createAccountDetailWithId();
+ element.change = {
+ ...createParsedChange(),
+ actions: {topic: {enabled: true}},
+ };
+ flush();
+
+ const label = element.shadowRoot!.querySelector(
+ '.topicEditableLabel'
+ ) as GrEditableLabel;
+ assert.ok(label);
+ const openStub = sinon.stub(label, 'open');
+ element.editTopic();
+ flush();
+
+ assert.isTrue(openStub.called);
+ });
+
+ suite('plugin endpoints', () => {
+ test('endpoint params', done => {
+ element.change = createParsedChange();
+ element.revision = createRevision();
+ interface MetadataGrEndpointDecorator extends GrEndpointDecorator {
+ plugin: PluginApi;
+ change: ParsedChangeInfo;
+ revision: RevisionInfo;
+ }
+ let hookEl: MetadataGrEndpointDecorator;
+ let plugin: PluginApi;
+ pluginApi.install(
+ p => {
+ plugin = p;
+ plugin
+ .hook('change-metadata-item')
+ .getLastAttached()
+ .then(el => (hookEl = el as MetadataGrEndpointDecorator));
+ },
+ '0.1',
+ 'http://some/plugins/url.html'
+ );
+ getPluginLoader().loadPlugins([]);
+ flush(() => {
+ assert.strictEqual(hookEl!.plugin, plugin);
+ assert.strictEqual(hookEl!.change, element.change);
+ assert.strictEqual(hookEl!.revision, element.revision);
+ done();
+ });
+ });
+ });
+});
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
index 4fd7337..716cd67 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
@@ -30,7 +30,7 @@
const INIT_PROPERTIES_TIMEOUT_MS = 10000;
@customElement('gr-endpoint-decorator')
-class GrEndpointDecorator extends GestureEventListeners(
+export class GrEndpointDecorator extends GestureEventListeners(
LegacyElementMixin(PolymerElement)
) {
static get template() {
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index 3b464a5..248e937 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -58,6 +58,8 @@
TimezoneOffset,
UserConfigInfo,
AccountDetailInfo,
+ Requirement,
+ RequirementType,
} from '../types/common';
import {
AccountsVisibility,
@@ -74,6 +76,7 @@
RevisionKind,
SubmitType,
TimeFormat,
+ RequirementStatus,
} from '../constants/constants';
import {formatDate} from '../utils/date-util';
import {GetDiffCommentsOutput} from '../services/services/gr-rest-api/gr-rest-api';
@@ -83,6 +86,8 @@
EditRevisionInfo,
ParsedChangeInfo,
} from '../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser';
+import {CommitInfoWithRequiredCommit} from '../elements/change/gr-change-metadata/gr-change-metadata';
+import {WebLinkInfo} from '../types/diff';
export function dateToTimestamp(date: Date): Timestamp {
const nanosecondSuffix = '.000000000';
@@ -194,6 +199,13 @@
};
}
+export function createCommitInfoWithRequiredCommit(): CommitInfoWithRequiredCommit {
+ return {
+ ...createCommit(),
+ commit: 'commit' as CommitId,
+ };
+}
+
export function createRevision(patchSetNum = 1): RevisionInfo {
return {
_number: patchSetNum as PatchSetNum,
@@ -413,3 +425,19 @@
project: TEST_PROJECT_NAME,
};
}
+
+export function createRequirement(): Requirement {
+ return {
+ status: RequirementStatus.OK,
+ fallbackText: '',
+ type: 'wip' as RequirementType,
+ };
+}
+
+export function createWebLinkInfo(): WebLinkInfo {
+ return {
+ name: 'gitiles',
+ url: '#',
+ image_url: 'gitiles.jpg',
+ };
+}