Merge changes I83f0153b,I9e4aa92a,I019ce1c6,I7b0bc949,I19d4515d, ...
* changes:
Migrate gr-diff-builder-unified_test from js to ts
Migrate gr-diff-cursor_test from js to ts
Migrate gr-diff-app-context-init_test from js to ts
Migrate gr-diff-group_test from js to ts
Migrate gr-diff_test from js to ts
Migrate gr-diff-builder-element from Polymer to plain class
Migrate gr-diff-builder-element_test from js to ts
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
index 24719e8..42ff8f6 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow.ts
@@ -10,6 +10,7 @@
import {configModelToken} from '../../../models/config/config-model';
import {resolve} from '../../../models/dependency';
import {
+ AccountDetailInfo,
AccountInfo,
ChangeInfo,
NumericChangeId,
@@ -32,6 +33,8 @@
import {getDisplayName} from '../../../utils/display-name-util';
import {AccountInputDetail} from '../../shared/gr-account-list/gr-account-list';
import '@polymer/iron-icon/iron-icon';
+import {getReplyByReason} from '../../../utils/attention-set-util';
+import {intersection} from '../../../utils/common-util';
@customElement('gr-change-list-reviewer-flow')
export class GrChangeListReviewerFlow extends LitElement {
@@ -72,6 +75,8 @@
private isLoggedIn = false;
+ private account?: AccountDetailInfo;
+
static override get styles() {
return css`
gr-dialog {
@@ -125,6 +130,11 @@
() => getAppContext().userModel.loggedIn$,
isLoggedIn => (this.isLoggedIn = isLoggedIn)
);
+ subscribe(
+ this,
+ () => getAppContext().userModel.account$,
+ account => (this.account = account)
+ );
}
override render() {
@@ -335,7 +345,8 @@
])
);
const inFlightActions = this.getBulkActionsModel().addReviewers(
- this.updatedAccountsByReviewerState
+ this.updatedAccountsByReviewerState,
+ getReplyByReason(this.account, this.serverConfig)
);
await allSettled(
@@ -383,13 +394,7 @@
const reviewersPerChange = this.selectedChanges.map(
change => change.reviewers[reviewerState] ?? []
);
- if (reviewersPerChange.length === 0) {
- return [];
- }
- // Gets reviewers present in all changes
- return reviewersPerChange.reduce((a, b) =>
- a.filter(reviewer => b.includes(reviewer))
- );
+ return intersection(reviewersPerChange);
}
private createSuggestionsProvider(
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow_test.ts
index edcad8f..e34a269 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-reviewer-flow/gr-change-list-reviewer-flow_test.ts
@@ -245,6 +245,14 @@
{reviewer: accounts[2]._account_id, state: ReviewerState.REVIEWER},
{reviewer: accounts[5]._account_id, state: ReviewerState.CC},
],
+ ignore_automatic_attention_set_rules: true,
+ // only the reviewer is added to the attention set, not the cc
+ add_to_attention_set: [
+ {
+ reason: '<GERRIT_ACCOUNT_1> replied on the change',
+ user: accounts[2]._account_id,
+ },
+ ],
},
]);
assert.sameDeepOrderedMembers(saveChangeReviewStub.secondCall.args, [
@@ -255,6 +263,14 @@
{reviewer: accounts[2]._account_id, state: ReviewerState.REVIEWER},
{reviewer: accounts[5]._account_id, state: ReviewerState.CC},
],
+ ignore_automatic_attention_set_rules: true,
+ // only the reviewer is added to the attention set, not the cc
+ add_to_attention_set: [
+ {
+ reason: '<GERRIT_ACCOUNT_1> replied on the change',
+ user: accounts[2]._account_id,
+ },
+ ],
},
]);
});
@@ -357,7 +373,7 @@
);
await flush();
- // prettier and shadoDom string don't agree on long text in divs
+ // prettier and shadowDom string don't agree on long text in divs
expect(element).shadowDom.to.equal(
/* prettier-ignore */
/* HTML */ `
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 105c0d8..3db1012 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -633,6 +633,12 @@
private connected$ = new BehaviorSubject(false);
+ /**
+ * For `connectedCallback()` to distinguish between connecting to the DOM for
+ * the first time or if just re-connecting.
+ */
+ private isFirstConnection = true;
+
/** Simply reflects the router-model value. */
// visible for testing
routerPatchNum?: PatchSetNum;
@@ -649,12 +655,29 @@
'fullscreen-overlay-opened',
() => this._handleHideBackgroundContent()
);
-
this.addEventListener('fullscreen-overlay-closed', () =>
this._handleShowBackgroundContent()
);
-
this.addEventListener('open-reply-dialog', () => this._openReplyDialog());
+ this.addEventListener('change-message-deleted', () => fireReload(this));
+ this.addEventListener('editable-content-save', e =>
+ this._handleCommitMessageSave(e)
+ );
+ this.addEventListener('editable-content-cancel', () =>
+ this._handleCommitMessageCancel()
+ );
+ this.addEventListener('open-fix-preview', e => this._onOpenFixPreview(e));
+ this.addEventListener('close-fix-preview', e => this._onCloseFixPreview(e));
+
+ this.addEventListener(EventType.SHOW_PRIMARY_TAB, e =>
+ this._setActivePrimaryTab(e)
+ );
+ this.addEventListener('reload', e => {
+ this.loadData(
+ /* isLocationChange= */ false,
+ /* clearPatchset= */ e.detail && e.detail.clearPatchset
+ );
+ });
}
private setupSubscriptions() {
@@ -699,24 +722,23 @@
override connectedCallback() {
super.connectedCallback();
+ this.firstConnectedCallback();
this.connected$.next(true);
- this.setupSubscriptions();
- this._throttledToggleChangeStar = throttleWrap<KeyboardEvent>(_ =>
- this._handleToggleChangeStar()
- );
- this._getServerConfig().then(config => {
- this._serverConfig = config;
- this._replyDisabled = false;
- });
- this._getLoggedIn().then(loggedIn => {
- this._loggedIn = loggedIn;
- if (loggedIn) {
- this.restApiService.getAccount().then(acct => {
- this._account = acct;
- });
- }
- });
+ // Make sure to reverse everything below this line in disconnectedCallback().
+ // Or consider using either firstConnectedCallback() or constructor().
+ this.setupSubscriptions();
+ document.addEventListener('visibilitychange', this.handleVisibilityChange);
+ document.addEventListener('scroll', this.handleScroll);
+ }
+
+ /**
+ * For initialization that should only happen once, not again when
+ * re-connecting to the DOM later.
+ */
+ private firstConnectedCallback() {
+ if (!this.isFirstConnection) return;
+ this.isFirstConnection = false;
getPluginLoader()
.awaitPluginsLoaded()
@@ -734,26 +756,21 @@
})
.then(() => this._initActiveTabs(this.params));
- this.addEventListener('change-message-deleted', () => fireReload(this));
- this.addEventListener('editable-content-save', e =>
- this._handleCommitMessageSave(e)
+ this._throttledToggleChangeStar = throttleWrap<KeyboardEvent>(_ =>
+ this._handleToggleChangeStar()
);
- this.addEventListener('editable-content-cancel', () =>
- this._handleCommitMessageCancel()
- );
- this.addEventListener('open-fix-preview', e => this._onOpenFixPreview(e));
- this.addEventListener('close-fix-preview', e => this._onCloseFixPreview(e));
- document.addEventListener('visibilitychange', this.handleVisibilityChange);
- document.addEventListener('scroll', this.handleScroll);
+ this._getServerConfig().then(config => {
+ this._serverConfig = config;
+ this._replyDisabled = false;
+ });
- this.addEventListener(EventType.SHOW_PRIMARY_TAB, e =>
- this._setActivePrimaryTab(e)
- );
- this.addEventListener('reload', e => {
- this.loadData(
- /* isLocationChange= */ false,
- /* clearPatchset= */ e.detail && e.detail.clearPatchset
- );
+ this._getLoggedIn().then(loggedIn => {
+ this._loggedIn = loggedIn;
+ if (loggedIn) {
+ this.restApiService.getAccount().then(acct => {
+ this._account = acct;
+ });
+ }
});
}
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
index 8d82e41..66df866 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
@@ -17,11 +17,9 @@
import '../../../test/common-test-setup-karma';
import '../../shared/gr-date-formatter/gr-date-formatter';
import './gr-file-list';
-import {createCommentApiMockWithTemplateElement} from '../../../test/mocks/comment-api';
import {FilesExpandedState} from '../gr-file-list-constants';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {runA11yAudit} from '../../../test/a11y-test-utils';
-import {html} from '@polymer/polymer/lib/utils/html-tag';
import {
listenOnce,
mockPromise,
@@ -61,12 +59,7 @@
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {GrEditFileControls} from '../../edit/gr-edit-file-controls/gr-edit-file-controls';
-const commentApiMock = createCommentApiMockWithTemplateElement(
- 'gr-file-list-comment-api-mock',
- html` <gr-file-list id="fileList"></gr-file-list> `
-);
-
-const basicFixture = fixtureFromElement(commentApiMock.is);
+const basicFixture = fixtureFromElement('gr-file-list');
suite('gr-diff a11y test', () => {
test('audit', async () => {
@@ -85,7 +78,6 @@
suite('gr-file-list tests', () => {
let element: GrFileList;
- let commentApiWrapper: any;
let saveStub: sinon.SinonStub;
@@ -103,8 +95,7 @@
// Element must be wrapped in an element with direct access to the
// comment API.
- commentApiWrapper = basicFixture.instantiate();
- element = commentApiWrapper.$.fileList;
+ element = basicFixture.instantiate();
element._loading = false;
element.diffPrefs = {} as DiffPreferencesInfo;
@@ -1976,8 +1967,7 @@
// Element must be wrapped in an element with direct access to the
// comment API.
- commentApiWrapper = basicFixture.instantiate();
- element = commentApiWrapper.$.fileList;
+ element = basicFixture.instantiate();
element.diffPrefs = {} as DiffPreferencesInfo;
element.change = {
...createParsedChange(),
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
index 27c445e..8e757da 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.ts
@@ -24,10 +24,8 @@
LabelNameToValueMap,
} from '../../../types/common';
import {GrLabelScoreRow} from '../gr-label-score-row/gr-label-score-row';
-import {getAppContext} from '../../../services/app-context';
import {
getTriggerVotes,
- showNewSubmitRequirements,
computeLabels,
Label,
computeOrderedLabelValues,
@@ -48,8 +46,6 @@
@property({type: Object})
account?: AccountInfo;
- private readonly flagsService = getAppContext().flagsService;
-
static override get styles() {
return [
fontStyles,
@@ -57,8 +53,6 @@
.scoresTable {
display: table;
width: 100%;
- }
- .scoresTable.newSubmitRequirements {
table-layout: fixed;
}
.mergedMessage,
@@ -91,19 +85,6 @@
}
override render() {
- if (showNewSubmitRequirements(this.flagsService, this.change)) {
- return this.renderNewSubmitRequirements();
- } else {
- return this.renderOldSubmitRequirements();
- }
- }
-
- private renderOldSubmitRequirements() {
- const labels = computeLabels(this.account, this.change);
- return html`${this.renderLabels(labels)}${this.renderErrorMessages()}`;
- }
-
- private renderNewSubmitRequirements() {
return html`${this.renderSubmitReqsLabels()}${this.renderTriggerVotes()}
${this.renderErrorMessages()}`;
}
@@ -145,13 +126,7 @@
}
private renderLabels(labels: Label[]) {
- const newSubReqs = showNewSubmitRequirements(
- this.flagsService,
- this.change
- );
- return html`<div
- class="scoresTable ${newSubReqs ? 'newSubmitRequirements' : ''}"
- >
+ return html`<div class="scoresTable">
${labels
.filter(
label =>
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts
index b9cb616..ac487f8 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts
@@ -17,10 +17,8 @@
import '../../../test/common-test-setup-karma';
import './gr-messages-list';
-import {createCommentApiMockWithTemplateElement} from '../../../test/mocks/comment-api';
import {CombinedMessage, GrMessagesList, TEST_ONLY} from './gr-messages-list';
import {MessageTag} from '../../../constants/constants';
-import {html} from '@polymer/polymer/lib/utils/html-tag';
import {
query,
queryAll,
@@ -43,16 +41,7 @@
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import {assertIsDefined} from '../../../utils/common-util';
-createCommentApiMockWithTemplateElement(
- 'gr-messages-list-comment-mock-api',
- html` <gr-messages-list id="messagesList"></gr-messages-list> `
-);
-
-const basicFixture = fixtureFromTemplate(html`
- <gr-messages-list-comment-mock-api>
- <gr-messages-list></gr-messages-list>
- </gr-messages-list-comment-mock-api>
-`);
+const basicFixture = fixtureFromElement('gr-messages-list');
const author = {
_account_id: 42 as AccountId,
@@ -99,8 +88,6 @@
let element: GrMessagesList;
let messages: ChangeMessageInfo[];
- let commentApiWrapper: any;
-
const getMessages = function () {
return queryAll<GrMessage>(element, 'gr-message');
};
@@ -156,13 +143,7 @@
stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
messages = generateRandomMessages(3);
- // Element must be wrapped in an element with direct access to the
- // comment API.
- commentApiWrapper = basicFixture.instantiate();
- element = queryAndAssert<GrMessagesList>(
- commentApiWrapper,
- '#messagesList'
- );
+ element = basicFixture.instantiate();
await element.getCommentsModel().reloadComments(0 as NumericChangeId);
element.messages = messages;
await flush();
@@ -507,8 +488,6 @@
let element: GrMessagesList;
let messages: ChangeMessageInfo[];
- let commentApiWrapper: any;
-
setup(() => {
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getDiffComments').returns(Promise.resolve({}));
@@ -529,13 +508,7 @@
}),
];
- // Element must be wrapped in an element with direct access to the
- // comment API.
- commentApiWrapper = basicFixture.instantiate();
- element = queryAndAssert<GrMessagesList>(
- commentApiWrapper,
- '#messagesList'
- );
+ element = basicFixture.instantiate();
element.messages = messages;
flush();
});
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
index 6740977..d5ce654 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
@@ -29,12 +29,7 @@
LabelInfo,
} from '../../../types/common';
import {hasOwnProperty} from '../../../utils/common-util';
-import {getAppContext} from '../../../services/app-context';
-import {
- getApprovalInfo,
- getCodeReviewLabel,
- showNewSubmitRequirements,
-} from '../../../utils/label-util';
+import {getApprovalInfo, getCodeReviewLabel} from '../../../utils/label-util';
import {sortReviewers} from '../../../utils/attention-set-util';
import {sharedStyles} from '../../../styles/shared-styles';
import {css} from 'lit';
@@ -68,8 +63,6 @@
@state() showAllReviewers = false;
- private readonly flagsService = getAppContext().flagsService;
-
static override get styles() {
return [
sharedStyles,
@@ -166,14 +159,12 @@
.vote=${this.computeVote(reviewer)}
.label=${this.computeCodeReviewLabel()}
>
- ${showNewSubmitRequirements(this.flagsService, this.change)
- ? html`<gr-vote-chip
- slot="vote-chip"
- .vote=${this.computeVote(reviewer)}
- .label=${this.computeCodeReviewLabel()}
- circle-shape
- ></gr-vote-chip>`
- : nothing}
+ <gr-vote-chip
+ slot="vote-chip"
+ .vote=${this.computeVote(reviewer)}
+ .label=${this.computeCodeReviewLabel()}
+ circle-shape
+ ></gr-vote-chip>
</gr-account-chip>
`;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
index dc501c8..2e48771 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
@@ -20,7 +20,6 @@
import '../../shared/revision-info/revision-info';
import './gr-patch-range-select';
import {GrPatchRangeSelect} from './gr-patch-range-select';
-import '../../../test/mocks/comment-api';
import {RevisionInfo as RevisionInfoClass} from '../../shared/revision-info/revision-info';
import {ChangeComments} from '../gr-comment-api/gr-comment-api';
import {stubRestApi} from '../../../test/test-utils';
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
index 48d6998..3fecd63 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
@@ -167,9 +167,6 @@
@property({type: Array})
removableValues?: AccountInput[];
- @property({type: Number})
- maxCount = 0;
-
/**
* Returns suggestion items
*/
@@ -203,7 +200,7 @@
.group {
--account-label-suffix: ' (group)';
}
- .pending-add {
+ .pendingAdd {
font-style: italic;
}
.list {
@@ -234,8 +231,7 @@
</div>
<gr-account-entry
borderless=""
- ?hidden=${(this.maxCount && this.maxCount <= this.accounts.length) ||
- this.readonly}
+ ?hidden=${this.readonly}
id="entry"
.placeholder=${this.placeholder}
@add=${this.handleAdd}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
index 7b3a93d..26566a3 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
@@ -400,16 +400,6 @@
assert.equal(element.accounts.length, 1);
});
- test('max-count', async () => {
- element.maxCount = 1;
- const acct = makeAccount();
- handleAdd({account: acct, count: 1});
- await element.updateComplete;
- assert.isTrue(
- queryAndAssert<GrAccountEntry>(element, '#entry').hasAttribute('hidden')
- );
- });
-
test('enter text calls suggestions provider', async () => {
const suggestions: Suggestion[] = [
{
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
index b1d3914..ee3bec7 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
@@ -18,11 +18,9 @@
import '../../../styles/gr-voting-styles';
import '../../../styles/shared-styles';
import '../gr-vote-chip/gr-vote-chip';
-import '../gr-account-label/gr-account-label';
import '../gr-account-chip/gr-account-chip';
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
-import '../gr-label/gr-label';
import '../gr-tooltip-content/gr-tooltip-content';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {
@@ -30,9 +28,7 @@
LabelInfo,
ApprovalInfo,
AccountId,
- isQuickLabelInfo,
isDetailedLabelInfo,
- LabelNameToInfoMap,
} from '../../../types/common';
import {LitElement, css, html} from 'lit';
import {customElement, property} from 'lit/decorators';
@@ -40,10 +36,8 @@
import {
canVote,
getApprovalInfo,
- getVotingRangeOrDefault,
hasNeutralStatus,
hasVoted,
- showNewSubmitRequirements,
valueString,
} from '../../../utils/label-util';
import {getAppContext} from '../../../services/app-context';
@@ -61,19 +55,6 @@
}
}
-enum LabelClassName {
- NEGATIVE = 'negative',
- POSITIVE = 'positive',
- MIN = 'min',
- MAX = 'max',
-}
-
-interface FormattedLabel {
- className?: LabelClassName;
- account: ApprovalInfo | AccountInfo;
- value: string;
-}
-
@customElement('gr-label-info')
export class GrLabelInfo extends LitElement {
@property({type: Object})
@@ -107,8 +88,6 @@
private readonly reporting = getAppContext().reportingService;
- private readonly flagsService = getAppContext().flagsService;
-
// TODO(TS): not used, remove later
_xhrPromise?: Promise<void>;
@@ -118,9 +97,6 @@
fontStyles,
votingStyles,
css`
- .placeholder {
- color: var(--deemphasized-text-color);
- }
.hidden {
display: none;
}
@@ -132,33 +108,6 @@
margin-right: var(--spacing-s);
padding: 1px;
}
- .max {
- background-color: var(--vote-color-approved);
- }
- .min {
- background-color: var(--vote-color-rejected);
- }
- .positive {
- background-color: var(--vote-color-recommended);
- border-radius: 12px;
- border: 1px solid var(--vote-outline-recommended);
- color: var(--chip-color);
- }
- .negative {
- background-color: var(--vote-color-disliked);
- border-radius: 12px;
- border: 1px solid var(--vote-outline-disliked);
- color: var(--chip-color);
- }
- .hidden {
- display: none;
- }
- td {
- vertical-align: top;
- }
- tr {
- min-height: var(--line-height-normal);
- }
gr-tooltip-content {
display: block;
}
@@ -173,17 +122,10 @@
gr-button[disabled] iron-icon {
color: var(--border-color);
}
- gr-account-label {
- --account-max-length: 100px;
- margin-right: var(--spacing-xs);
- }
iron-icon {
height: calc(var(--line-height-normal) - 2px);
width: calc(var(--line-height-normal) - 2px);
}
- .labelValueContainer:not(:first-of-type) td {
- padding-top: var(--spacing-s);
- }
.reviewer-row {
padding-top: var(--spacing-s);
}
@@ -208,14 +150,6 @@
}
override render() {
- if (showNewSubmitRequirements(this.flagsService, this.change)) {
- return this.renderNewSubmitRequirements();
- } else {
- return this.renderOldSubmitRequirements();
- }
- }
-
- private renderNewSubmitRequirements() {
const labelInfo = this.labelInfo;
if (!labelInfo) return;
const reviewers = (this.change?.reviewers['REVIEWER'] ?? [])
@@ -238,23 +172,6 @@
</div>`;
}
- private renderOldSubmitRequirements() {
- const labelInfo = this.labelInfo;
- return html` <p
- class="placeholder ${this.computeShowPlaceholder(
- labelInfo,
- this.change?.labels
- )}"
- >
- No votes
- </p>
- <table>
- ${this.mapLabelInfo(labelInfo, this.account, this.change?.labels).map(
- mappedLabel => this.renderLabel(mappedLabel)
- )}
- </table>`;
- }
-
renderReviewerVote(reviewer: AccountInfo) {
const labelInfo = this.labelInfo;
if (!labelInfo) return;
@@ -285,30 +202,6 @@
</div>`;
}
- renderLabel(mappedLabel: FormattedLabel) {
- const {labelInfo, change} = this;
- return html` <tr class="labelValueContainer">
- <td>
- <gr-tooltip-content
- has-tooltip
- title=${this._computeValueTooltip(labelInfo, mappedLabel.value)}
- >
- <gr-label class="${mappedLabel.className} voteChip font-small">
- ${mappedLabel.value}
- </gr-label>
- </gr-tooltip-content>
- </td>
- <td>
- <gr-account-label
- clickable
- .account=${mappedLabel.account}
- .change=${change}
- ></gr-account-label>
- </td>
- <td>${this.renderRemoveVote(mappedLabel.account)}</td>
- </tr>`;
- }
-
private renderVoteAbility(reviewer: AccountInfo) {
if (this.labelInfo && isDetailedLabelInfo(this.labelInfo)) {
const approvalInfo = getApprovalInfo(this.labelInfo, reviewer);
@@ -341,83 +234,6 @@
}
/**
- * This method also listens on change.labels.*,
- * to trigger computation when a label is removed from the change.
- *
- * The third parameter is just for *triggering* computation.
- */
- private mapLabelInfo(
- labelInfo?: LabelInfo,
- account?: AccountInfo,
- _?: LabelNameToInfoMap
- ): FormattedLabel[] {
- const result: FormattedLabel[] = [];
- if (!labelInfo) {
- return result;
- }
- if (!isDetailedLabelInfo(labelInfo)) {
- if (
- isQuickLabelInfo(labelInfo) &&
- (labelInfo.rejected || labelInfo.approved)
- ) {
- const ok = labelInfo.approved || !labelInfo.rejected;
- return [
- {
- value: ok ? '👍️' : '👎️',
- className: ok ? LabelClassName.POSITIVE : LabelClassName.NEGATIVE,
- // executed only if approved or rejected is not undefined
- account: ok ? labelInfo.approved! : labelInfo.rejected!,
- },
- ];
- }
- return result;
- }
-
- // Sort votes by positivity.
- // TODO(TS): maybe mark value as required if always present
- const votes = (labelInfo.all || []).sort(
- (a, b) => (a.value || 0) - (b.value || 0)
- );
- const votingRange = getVotingRangeOrDefault(labelInfo);
- for (const label of votes) {
- if (
- label.value &&
- (!isQuickLabelInfo(labelInfo) ||
- label.value !== labelInfo.default_value)
- ) {
- let labelClassName;
- let labelValPrefix = '';
- if (label.value > 0) {
- labelValPrefix = '+';
- if (label.value === votingRange.max) {
- labelClassName = LabelClassName.MAX;
- } else {
- labelClassName = LabelClassName.POSITIVE;
- }
- } else if (label.value < 0) {
- if (label.value === votingRange.min) {
- labelClassName = LabelClassName.MIN;
- } else {
- labelClassName = LabelClassName.NEGATIVE;
- }
- }
- const formattedLabel: FormattedLabel = {
- value: `${labelValPrefix}${label.value}`,
- className: labelClassName,
- account: label,
- };
- if (label._account_id === account?._account_id) {
- // Put self-votes at the top.
- result.unshift(formattedLabel);
- } else {
- result.push(formattedLabel);
- }
- }
- }
- return result;
- }
-
- /**
* A user is able to delete a vote iff the mutable property is true and the
* reviewer that left the vote exists in the list of removable_reviewers
* received from the backend.
@@ -488,39 +304,4 @@
}
return labelInfo.values[score];
}
-
- /**
- * This method also listens change.labels.* in
- * order to trigger computation when a label is removed from the change.
- *
- * The second parameter is just for *triggering* computation.
- */
- private computeShowPlaceholder(
- labelInfo?: LabelInfo,
- _?: LabelNameToInfoMap
- ) {
- if (!labelInfo) {
- return '';
- }
- if (
- !isDetailedLabelInfo(labelInfo) &&
- isQuickLabelInfo(labelInfo) &&
- (labelInfo.rejected || labelInfo.approved)
- ) {
- return 'hidden';
- }
-
- if (isDetailedLabelInfo(labelInfo) && labelInfo.all) {
- for (const label of labelInfo.all) {
- if (
- label.value &&
- (!isQuickLabelInfo(labelInfo) ||
- label.value !== labelInfo.default_value)
- ) {
- return 'hidden';
- }
- }
- }
- return '';
- }
}
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.ts
index 0ac49a7..f1336b4 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.ts
@@ -20,20 +20,18 @@
import {
isHidden,
mockPromise,
- queryAll,
queryAndAssert,
stubRestApi,
} from '../../../test/test-utils';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import {GrLabelInfo} from './gr-label-info';
import {GrButton} from '../gr-button/gr-button';
-import {GrLabel} from '../gr-label/gr-label';
import {
createAccountWithIdNameAndEmail,
+ createDetailedLabelInfo,
createParsedChange,
} from '../../../test/test-data-generators';
-import {LabelInfo} from '../../../types/common';
-import {GrAccountLabel} from '../gr-account-label/gr-account-label';
+import {ApprovalInfo, LabelInfo} from '../../../types/common';
const basicFixture = fixtureFromElement('gr-label-info');
@@ -41,12 +39,51 @@
let element: GrLabelInfo;
const account = createAccountWithIdNameAndEmail(5);
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
// Needed to trigger computed bindings.
element.account = {};
- element.change = {...createParsedChange(), labels: {}};
+ element.change = {
+ ...createParsedChange(),
+ labels: {},
+ reviewers: {
+ REVIEWER: [account],
+ CC: [],
+ },
+ };
+ const approval: ApprovalInfo = {
+ value: 2,
+ _account_id: account._account_id,
+ };
+ element.labelInfo = {
+ ...createDetailedLabelInfo(),
+ all: [approval],
+ };
+ await element.updateComplete;
+ });
+
+ test('renders', () => {
+ expect(element).shadowDom.to.equal(/* HTML */ `<div>
+ <div class="reviewer-row">
+ <gr-account-chip>
+ <gr-vote-chip circle-shape="" slot="vote-chip"> </gr-vote-chip>
+ </gr-account-chip>
+ <gr-tooltip-content has-tooltip="" title="Remove vote">
+ <gr-button
+ aria-disabled="false"
+ aria-label="Remove vote"
+ class="deleteBtn hidden"
+ data-account-id="5"
+ link=""
+ role="button"
+ tabindex="0"
+ >
+ <iron-icon icon="gr-icons:delete"> </iron-icon>
+ </gr-button>
+ </gr-tooltip-content>
+ </div>
+ </div>`);
});
suite('remove reviewer votes', () => {
@@ -62,6 +99,10 @@
element.change = {
...createParsedChange(),
labels: {'Code-Review': label},
+ reviewers: {
+ REVIEWER: [account],
+ CC: [],
+ },
};
element.labelInfo = label;
element.label = 'Code-Review';
@@ -108,101 +149,6 @@
});
});
- suite('label color and order', () => {
- test('valueless label rejected', async () => {
- element.labelInfo = {rejected: {name: 'someone'}};
- await element.updateComplete;
- const labels = queryAll<GrLabel>(element, 'gr-label');
- assert.isTrue(labels[0].classList.contains('negative'));
- });
-
- test('valueless label approved', async () => {
- element.labelInfo = {approved: {name: 'someone'}};
- await element.updateComplete;
- const labels = queryAll<GrLabel>(element, 'gr-label');
- assert.isTrue(labels[0].classList.contains('positive'));
- });
-
- test('-2 to +2', async () => {
- element.labelInfo = {
- all: [
- {value: 2, name: 'user 2'},
- {value: 1, name: 'user 1'},
- {value: -1, name: 'user 3'},
- {value: -2, name: 'user 4'},
- ],
- values: {
- '-2': 'Awful',
- '-1': "Don't submit as-is",
- ' 0': 'No score',
- '+1': 'Looks good to me',
- '+2': 'Ready to submit',
- },
- };
- await element.updateComplete;
- const labels = queryAll<GrLabel>(element, 'gr-label');
- assert.isTrue(labels[0].classList.contains('max'));
- assert.isTrue(labels[1].classList.contains('positive'));
- assert.isTrue(labels[2].classList.contains('negative'));
- assert.isTrue(labels[3].classList.contains('min'));
- });
-
- test('-1 to +1', async () => {
- element.labelInfo = {
- all: [
- {value: 1, name: 'user 1'},
- {value: -1, name: 'user 2'},
- ],
- values: {
- '-1': "Don't submit as-is",
- ' 0': 'No score',
- '+1': 'Looks good to me',
- },
- };
- await element.updateComplete;
- const labels = queryAll<GrLabel>(element, 'gr-label');
- assert.isTrue(labels[0].classList.contains('max'));
- assert.isTrue(labels[1].classList.contains('min'));
- });
-
- test('0 to +2', async () => {
- element.labelInfo = {
- all: [
- {value: 1, name: 'user 2'},
- {value: 2, name: 'user '},
- ],
- values: {
- ' 0': "Don't submit as-is",
- '+1': 'No score',
- '+2': 'Looks good to me',
- },
- };
- await element.updateComplete;
- const labels = queryAll<GrLabel>(element, 'gr-label');
- assert.isTrue(labels[0].classList.contains('max'));
- assert.isTrue(labels[1].classList.contains('positive'));
- });
-
- test('self votes at top', async () => {
- const otherAccount = createAccountWithIdNameAndEmail(8);
- element.account = account;
- element.labelInfo = {
- all: [
- {...otherAccount, value: 1},
- {...account, value: -1},
- ],
- values: {
- '-1': "Don't submit as-is",
- ' 0': 'No score',
- '+1': 'Looks good to me',
- },
- };
- await element.updateComplete;
- const chips = queryAll<GrAccountLabel>(element, 'gr-account-label');
- assert.equal(chips[0].account!._account_id, element.account._account_id);
- });
- });
-
test('_computeValueTooltip', () => {
// Existing label.
let labelInfo: LabelInfo = {values: {0: 'Baz'}};
@@ -218,49 +164,4 @@
score = '0';
assert.equal(element._computeValueTooltip(labelInfo, score), '');
});
-
- test('placeholder', async () => {
- const values = {
- '0': 'No score',
- '+1': 'good',
- '+2': 'excellent',
- '-1': 'bad',
- '-2': 'terrible',
- };
- element.labelInfo = {};
- await element.updateComplete;
- assert.isFalse(
- isHidden(queryAndAssert<HTMLParagraphElement>(element, '.placeholder'))
- );
- element.labelInfo = {all: [], values};
- await element.updateComplete;
- assert.isFalse(
- isHidden(queryAndAssert<HTMLParagraphElement>(element, '.placeholder'))
- );
- element.labelInfo = {all: [{value: 1}], values};
- await element.updateComplete;
- assert.isTrue(
- isHidden(queryAndAssert<HTMLParagraphElement>(element, '.placeholder'))
- );
- element.labelInfo = {rejected: account};
- await element.updateComplete;
- assert.isTrue(
- isHidden(queryAndAssert<HTMLParagraphElement>(element, '.placeholder'))
- );
- element.labelInfo = {rejected: account, all: [{value: 1}], values};
- await element.updateComplete;
- assert.isTrue(
- isHidden(queryAndAssert<HTMLParagraphElement>(element, '.placeholder'))
- );
- element.labelInfo = {approved: account};
- await element.updateComplete;
- assert.isTrue(
- isHidden(queryAndAssert<HTMLParagraphElement>(element, '.placeholder'))
- );
- element.labelInfo = {approved: account, all: [{value: 1}], values};
- await element.updateComplete;
- assert.isTrue(
- isHidden(queryAndAssert<HTMLParagraphElement>(element, '.placeholder'))
- );
- });
});
diff --git a/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts b/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
deleted file mode 100644
index 842b35e..0000000
--- a/polygerrit-ui/app/elements/shared/gr-label/gr-label.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 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.
- */
-
-/**
- * @fileoverview Consider removing this element as
- * its functionality seems to be duplicated with gr-tooltip and only
- * used in gr-label-info.
- */
-
-import {html, LitElement} from 'lit';
-import {customElement} from 'lit/decorators';
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-label': GrLabel;
- }
-}
-
-@customElement('gr-label')
-export class GrLabel extends LitElement {
- static override get styles() {
- return [];
- }
-
- override render() {
- return html` <slot></slot> `;
- }
-}
diff --git a/polygerrit-ui/app/elements/shared/gr-label/gr-label_html.ts b/polygerrit-ui/app/elements/shared/gr-label/gr-label_html.ts
deleted file mode 100644
index 94196df..0000000
--- a/polygerrit-ui/app/elements/shared/gr-label/gr-label_html.ts
+++ /dev/null
@@ -1,19 +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 {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html` <slot></slot> `;
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
index c276f79..c80a1cf 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
@@ -10,13 +10,18 @@
ChangeStatus,
ReviewerState,
AccountInfo,
+ AccountId,
} from '../../api/rest-api';
import {Model} from '../model';
import {Finalizable} from '../../services/registry';
import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
import {define} from '../dependency';
import {select} from '../../utils/observable-util';
-import {ReviewInput, ReviewerInput} from '../../types/common';
+import {
+ ReviewInput,
+ ReviewerInput,
+ AttentionSetInput,
+} from '../../types/common';
export const bulkActionsModelToken =
define<BulkActionsModel>('bulk-actions-model');
@@ -154,14 +159,15 @@
}
addReviewers(
- changedReviewers: Map<ReviewerState, AccountInfo[]>
+ changedReviewers: Map<ReviewerState, AccountInfo[]>,
+ reason: string
): Promise<Response>[] {
const current = this.subject$.getValue();
const changes = current.selectedChangeNums.map(
changeNum => current.allChanges.get(changeNum)!
);
return changes.map(change => {
- const reviewersNewToChange = [
+ const reviewersNewToChange: ReviewerInput[] = [
ReviewerState.REVIEWER,
ReviewerState.CC,
].flatMap(state =>
@@ -170,8 +176,20 @@
if (reviewersNewToChange.length === 0) {
return Promise.resolve(new Response());
}
+ const attentionSetUpdates: AttentionSetInput[] = reviewersNewToChange
+ .filter(reviewerInput => reviewerInput.state === ReviewerState.REVIEWER)
+ .map(reviewerInput => {
+ return {
+ // TODO: Once Groups are supported, filter them out and only add
+ // Accounts to the attention set, just like gr-reply-dialog.
+ user: reviewerInput.reviewer as AccountId,
+ reason,
+ };
+ });
const reviewInput: ReviewInput = {
reviewers: reviewersNewToChange,
+ ignore_automatic_attention_set_rules: true,
+ add_to_attention_set: attentionSetUpdates,
};
return this.restApiService.saveChangeReview(
change._number,
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
index 5347b41..aaf7123 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
@@ -236,7 +236,8 @@
new Map([
[ReviewerState.REVIEWER, [accounts[0]]],
[ReviewerState.CC, [accounts[1]]],
- ])
+ ]),
+ '<GERRIT_ACCOUNT_12345> replied on the change'
);
// changes[0] is not updated since it already has the reviewer & CC
@@ -249,6 +250,13 @@
{reviewer: accounts[0]._account_id, state: ReviewerState.REVIEWER},
{reviewer: accounts[1]._account_id, state: ReviewerState.CC},
],
+ ignore_automatic_attention_set_rules: true,
+ add_to_attention_set: [
+ {
+ reason: '<GERRIT_ACCOUNT_12345> replied on the change',
+ user: accounts[0]._account_id,
+ },
+ ],
},
]);
});
diff --git a/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts b/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
index 5f77e8a..028b2af 100644
--- a/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
+++ b/polygerrit-ui/app/services/gr-auth/gr-auth_impl.ts
@@ -62,7 +62,7 @@
static CREDS_EXPIRED_MSG = 'Credentials expired.';
- private authCheckPromise?: Promise<Response>;
+ private authCheckPromise?: Promise<boolean>;
private _last_auth_check_time: number = Date.now();
@@ -100,37 +100,37 @@
Date.now() - this._last_auth_check_time > MAX_AUTH_CHECK_WAIT_TIME_MS
) {
// Refetch after last check expired
- this.authCheckPromise = fetch(`${this.baseUrl}/auth-check`);
+ this.authCheckPromise = fetch(`${this.baseUrl}/auth-check`)
+ .then(res => {
+ // Make a call that requires loading the body of the request. This makes it so that the browser
+ // can close the request even though callers of this method might only ever read headers.
+ // See https://stackoverflow.com/questions/45816743/how-to-solve-this-caution-request-is-not-finished-yet-in-chrome
+ try {
+ res.clone().text();
+ } catch {
+ // Ignore error
+ }
+
+ // auth-check will return 204 if authed
+ // treat the rest as unauthed
+ if (res.status === 204) {
+ this._setStatus(Auth.STATUS.AUTHED);
+ return true;
+ } else {
+ this._setStatus(Auth.STATUS.NOT_AUTHED);
+ return false;
+ }
+ })
+ .catch(() => {
+ this._setStatus(AuthStatus.ERROR);
+ // Reset authCheckPromise to avoid caching the failed promise
+ this.authCheckPromise = undefined;
+ return false;
+ });
this._last_auth_check_time = Date.now();
}
- return this.authCheckPromise
- .then(res => {
- // Make a call that requires loading the body of the request. This makes it so that the browser
- // can close the request even though callers of this method might only ever read headers.
- // See https://stackoverflow.com/questions/45816743/how-to-solve-this-caution-request-is-not-finished-yet-in-chrome
- try {
- res.clone().text();
- } catch {
- // Ignore error
- }
-
- // auth-check will return 204 if authed
- // treat the rest as unauthed
- if (res.status === 204) {
- this._setStatus(Auth.STATUS.AUTHED);
- return true;
- } else {
- this._setStatus(Auth.STATUS.NOT_AUTHED);
- return false;
- }
- })
- .catch(() => {
- this._setStatus(AuthStatus.ERROR);
- // Reset authCheckPromise to avoid caching the failed promise
- this.authCheckPromise = undefined;
- return false;
- });
+ return this.authCheckPromise;
}
clearCache() {
diff --git a/polygerrit-ui/app/test/mocks/comment-api.js b/polygerrit-ui/app/test/mocks/comment-api.js
deleted file mode 100644
index fc4599d..0000000
--- a/polygerrit-ui/app/test/mocks/comment-api.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 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 {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
-
-/**
- * This is an "abstract" class for tests. The descendant must define a template
- * for this element and a tagName - see createCommentApiMockWithTemplateElement below
- */
-class CommentApiMock extends LegacyElementMixin(PolymerElement) {
- static get properties() {
- return {
- _changeComments: Object,
- };
- }
-}
-
-/**
- * Creates a new element which is descendant of CommentApiMock with specified
- * template. Additionally, the method registers a tagName for this element.
- *
- * Each tagName must be a unique accross all tests.
- */
-export function createCommentApiMockWithTemplateElement(tagName, template) {
- const elementClass = class extends CommentApiMock {
- static get is() { return tagName; }
-
- static get template() { return template; }
- };
- customElements.define(tagName, elementClass);
- return elementClass;
-}
diff --git a/polygerrit-ui/app/utils/common-util.ts b/polygerrit-ui/app/utils/common-util.ts
index 6ccf770..95b753c 100644
--- a/polygerrit-ui/app/utils/common-util.ts
+++ b/polygerrit-ui/app/utils/common-util.ts
@@ -159,7 +159,8 @@
/**
* Returns the elements that are present in every sub-array. If a compareBy
- * predicate is passed in, it will be used instead of strict equality.
+ * predicate is passed in, it will be used instead of strict equality. A new
+ * array is always returned even if there is already just a single array.
*/
export function intersection<T>(
arrays: T[][],
@@ -171,6 +172,9 @@
if (arrays.length === 0) {
return [];
}
+ if (arrays.length === 1) {
+ return [...arrays[0]];
+ }
return arrays.reduce((result, array) =>
result.filter(t => array.find(u => compareBy(t, u)))
);
diff --git a/polygerrit-ui/app/utils/common-util_test.ts b/polygerrit-ui/app/utils/common-util_test.ts
index 8cc523a..0adfaa6 100644
--- a/polygerrit-ui/app/utils/common-util_test.ts
+++ b/polygerrit-ui/app/utils/common-util_test.ts
@@ -75,8 +75,11 @@
});
test('intersections', () => {
+ const arrayWithValues = [1, 2, 3];
assert.sameDeepMembers(intersection([]), []);
- assert.sameDeepMembers(intersection([[1, 2, 3]]), [1, 2, 3]);
+ assert.sameDeepMembers(intersection([arrayWithValues]), arrayWithValues);
+ // a new array is returned even if a single array is provided.
+ assert.notStrictEqual(intersection([arrayWithValues]), arrayWithValues);
assert.sameDeepMembers(
intersection([
[1, 2, 3],