commit 8b016335edc88383d210b7b649ac4190c3afa593 Author: Dmitrii Filippov Date: Wed Oct 7 08:16:55 2020 +0200 Convert files to typescript The change converts the following files to typescript: * elements/change/gr-messages-list/gr-messages-list.ts Change-Id: Ic449dedcf3024c4762f0dd811c66712e31131dca diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts index ad95b2a..8b0cf4b 100644 --- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts +++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts @@ -39,6 +39,7 @@ import { ReviewInputTag, VotingRangeInfo, NumericChangeId, + ChangeMessageId, } from '../../../types/common'; import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; import {CommentThread} from '../../../utils/comment-util'; @@ -53,6 +54,10 @@ declare global { } } +export interface MessageAnchorTapDetail { + id: ChangeMessageId; +} + export interface GrMessage { $: { restAPI: RestApiService & Element; @@ -438,11 +443,16 @@ export class GrMessage extends GestureEventListeners( _handleAnchorClick(e: Event) { e.preventDefault(); + // The element which triggers _handleAnchorClick is rendered only if + // message.id defined: the elemenet is wrapped in dom-if if="[[message.id]]" + const detail: MessageAnchorTapDetail = { + id: this.message!.id, + }; this.dispatchEvent( new CustomEvent('message-anchor-tap', { bubbles: true, composed: true, - detail: {id: this.message?.id}, + detail, }) ); } diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts index e85f9e4..8557c10 100644 --- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts +++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts @@ -14,53 +14,99 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '@polymer/paper-toggle-button/paper-toggle-button.js'; -import '../../shared/gr-button/gr-button.js'; -import '../../shared/gr-icons/gr-icons.js'; -import '../gr-message/gr-message.js'; -import '../../../styles/shared-styles.js'; -import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js'; -import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js'; -import {PolymerElement} from '@polymer/polymer/polymer-element.js'; -import {htmlTemplate} from './gr-messages-list_html.js'; +import '@polymer/paper-toggle-button/paper-toggle-button'; +import '../../shared/gr-button/gr-button'; +import '../../shared/gr-icons/gr-icons'; +import '../gr-message/gr-message'; +import '../../../styles/shared-styles'; +import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners'; +import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin'; +import {PolymerElement} from '@polymer/polymer/polymer-element'; +import {htmlTemplate} from './gr-messages-list_html'; import { KeyboardShortcutMixin, - Shortcut, ShortcutSection, -} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js'; -import {parseDate} from '../../../utils/date-util.js'; -import {MessageTag} from '../../../constants/constants.js'; -import {appContext} from '../../../services/app-context.js'; + Shortcut, + ShortcutSection, +} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin'; +import {parseDate} from '../../../utils/date-util'; +import {MessageTag} from '../../../constants/constants'; +import {appContext} from '../../../services/app-context'; +import {customElement, property} from '@polymer/decorators'; +import { + ChangeId, + ChangeMessageId, + ChangeMessageInfo, + ChangeViewChangeInfo, + LabelNameToInfoMap, + NumericChangeId, + PatchSetNum, + RepoName, + ReviewerUpdateInfo, + VotingRangeInfo, +} from '../../../types/common'; +import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api'; +import {CommentThread, isRobot} from '../../../utils/comment-util'; +import {GrMessage, MessageAnchorTapDetail} from '../gr-message/gr-message'; +import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces'; +import {FormattedReviewerUpdateInfo} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser'; +import {DomRepeat} from '@polymer/polymer/lib/elements/dom-repeat'; +import {getVotingRange} from '../../../utils/label-util'; /** * The content of the enum is also used in the UI for the button text. - * - * @enum {string} */ -const ExpandAllState = { - EXPAND_ALL: 'Expand All', - COLLAPSE_ALL: 'Collapse All', +enum ExpandAllState { + EXPAND_ALL = 'Expand All', + COLLAPSE_ALL = 'Collapse All', +} + +interface TagsCountReportInfo { + [tag: string]: number; + all: number; +} + +type CombinedMessage = Omit< + FormattedReviewerUpdateInfo | ChangeMessageInfo, + 'tag' +> & { + _revision_number?: PatchSetNum; + _index?: number; + expanded?: boolean; + isImportant?: boolean; + commentThreads?: CommentThread[]; + tag?: string; }; +function isChangeMessageInfo(x: CombinedMessage): x is ChangeMessageInfo { + return (x as ChangeMessageInfo).id !== undefined; +} + +function getMessageId(x: CombinedMessage): ChangeMessageId | undefined { + return isChangeMessageInfo(x) ? x.id : undefined; +} + /** * Computes message author's comments for this change message. The backend * sets comment.change_message_id for matching, so this computation is fairly * straightforward. */ -function computeThreads(message, allMessages, changeComments) { - if ([message, allMessages, changeComments].includes(undefined)) { - return []; - } +function computeThreads( + message: CombinedMessage, + changeComments: ChangeComments +): CommentThread[] { if (message._index === undefined) { return []; } - - return changeComments.getAllThreadsForChange().filter( - thread => thread.comments.map(comment => { + const messageId = getMessageId(message); + return changeComments.getAllThreadsForChange().filter(thread => + thread.comments + .map(comment => { // collapse all by default comment.collapsed = true; return comment; - }).some(comment => { - const condition = comment.change_message_id === message.id; + }) + .some(comment => { + const condition = comment.change_message_id === messageId; // Since getAllThreadsForChange() always returns a new copy of // all comments we can modify them here without worrying about // polluting other threads. @@ -84,13 +130,15 @@ function computeThreads(message, allMessages, changeComments) { * 3. Everything beyond the ~ character is cut off from the tag. That gives * tools control over which messages will be hidden. */ -function computeTag(message) { +function computeTag(message: CombinedMessage) { if (!message.tag) { const threads = message.commentThreads || []; - const comments = threads.map( - t => t.comments.find(c => c.change_message_id === message.id)); - const isRobot = comments.some(c => c && !!c.robot_id); - return isRobot ? 'autogenerated:has-robot-comments' : undefined; + const messageId = getMessageId(message); + const comments = threads.map(t => + t.comments.find(c => c.change_message_id === messageId) + ); + const hasRobotComments = comments.some(isRobot); + return hasRobotComments ? 'autogenerated:has-robot-comments' : undefined; } if (message.tag === MessageTag.TAG_NEW_WIP_PATCHSET) { @@ -115,12 +163,17 @@ function computeTag(message) { * for reviewer updates. Other messages should typically have the revision * number already set. */ -function computeRevision(message, allMessages) { - if (message._revision_number > 0) return message._revision_number; - let revision = 0; +function computeRevision( + message: CombinedMessage, + allMessages: CombinedMessage[] +): PatchSetNum | undefined { + if (message._revision_number && message._revision_number > 0) + return message._revision_number; + let revision: PatchSetNum = 0 as PatchSetNum; for (const m of allMessages) { if (m.date > message.date) break; - if (m._revision_number > revision) revision = m._revision_number; + if (m._revision_number && m._revision_number > revision) + revision = m._revision_number; } return revision > 0 ? revision : undefined; } @@ -133,12 +186,16 @@ function computeRevision(message, allMessages) { * Autogenerated messages are unimportant, if there is a message with the same * tag and a higher revision number. */ -function computeIsImportant(message, allMessages) { +function computeIsImportant( + message: CombinedMessage, + allMessages: CombinedMessage[] +) { if (!message.tag) return true; - const hasSameTag = m => m.tag === message.tag; + const hasSameTag = (m: CombinedMessage) => m.tag === message.tag; const revNumber = message._revision_number || 0; - const hasHigherRevisionNumber = m => m._revision_number > revNumber; + const hasHigherRevisionNumber = (m: CombinedMessage) => + (m._revision_number || 0) > revNumber; return !allMessages.filter(hasSameTag).some(hasHigherRevisionNumber); } @@ -149,92 +206,72 @@ export const TEST_ONLY = { computeIsImportant, }; -/** - * @extends PolymerElement - */ -class GrMessagesList extends KeyboardShortcutMixin( - GestureEventListeners( - LegacyElementMixin( - PolymerElement))) { - static get template() { return htmlTemplate; } - - static get is() { return 'gr-messages-list'; } - - static get properties() { - return { - /** @type {?} */ - change: Object, - changeNum: Number, - /** - * These are just the change messages. They are combined with reviewer - * updates below. So _combinedMessages is the more important property. - */ - messages: { - type: Array, - value() { return []; }, - }, - /** - * These are just the reviewer updates. They are combined with change - * messages above. So _combinedMessages is the more important property. - */ - reviewerUpdates: { - type: Array, - value() { return []; }, - }, - changeComments: Object, - projectName: String, - showReplyButtons: { - type: Boolean, - value: false, - }, - labels: Object, - - /** - * Keeps track of the state of the "Expand All" toggle button. Note that - * you can individually expand/collapse some messages without affecting - * the toggle button's state. - * - * @type {ExpandAllState} - */ - _expandAllState: { - type: String, - value: ExpandAllState.EXPAND_ALL, - }, - _expandAllTitle: { - type: String, - computed: '_computeExpandAllTitle(_expandAllState)', - }, - - _showAllActivity: { - type: Boolean, - value: false, - observer: '_observeShowAllActivity', - }, - /** - * The merged array of change messages and reviewer updates. - */ - _combinedMessages: { - type: Array, - computed: '_computeCombinedMessages(messages, reviewerUpdates, ' - + 'changeComments)', - observer: '_combinedMessagesChanged', - }, - - _labelExtremes: { - type: Object, - computed: '_computeLabelExtremes(labels.*)', - }, - }; - } +export interface GrMessagesList { + $: { + messageRepeat: DomRepeat; + }; +} - constructor() { - super(); - this.reporting = appContext.reportingService; +@customElement('gr-messages-list') +export class GrMessagesList extends KeyboardShortcutMixin( + GestureEventListeners(LegacyElementMixin(PolymerElement)) +) { + static get template() { + return htmlTemplate; } - scrollToMessage(messageID) { + @property({type: Object}) + change?: ChangeViewChangeInfo; + + @property({type: String}) + changeNum?: ChangeId | NumericChangeId; + + @property({type: Array}) + messages: ChangeMessageInfo[] = []; + + @property({type: Array}) + reviewerUpdates: ReviewerUpdateInfo[] = []; + + @property({type: Object}) + changeComments?: ChangeComments; + + @property({type: String}) + projectName?: RepoName; + + @property({type: Boolean}) + showReplyButtons = false; + + @property({type: Object}) + labels?: LabelNameToInfoMap; + + @property({type: String}) + _expandAllState = ExpandAllState.EXPAND_ALL; + + @property({type: String, computed: '_computeExpandAllTitle(_expandAllState)'}) + _expandAllTitle = ''; + + @property({type: Boolean, observer: '_observeShowAllActivity'}) + _showAllActivity = false; + + @property({ + type: Array, + computed: + '_computeCombinedMessages(messages, reviewerUpdates, ' + + 'changeComments)', + observer: '_combinedMessagesChanged', + }) + _combinedMessages: CombinedMessage[] = []; + + @property({type: Object, computed: '_computeLabelExtremes(labels.*)'}) + _labelExtremes: {[lableName: string]: VotingRangeInfo} = {}; + + private readonly reporting = appContext.reportingService; + + scrollToMessage(messageID: string) { const selector = `[data-message-id="${messageID}"]`; - const el = this.shadowRoot.querySelector(selector); + const el = this.shadowRoot!.querySelector(selector) as + | GrMessage + | undefined; if (!el && this._showAllActivity) { console.warn(`Failed to scroll to message: ${messageID}`); @@ -248,16 +285,18 @@ class GrMessagesList extends KeyboardShortcutMixin( el.set('message.expanded', true); let top = el.offsetTop; - for (let offsetParent = el.offsetParent; + for ( + let offsetParent = el.offsetParent as HTMLElement | null; offsetParent; - offsetParent = offsetParent.offsetParent) { + offsetParent = offsetParent.offsetParent as HTMLElement | null + ) { top += offsetParent.offsetTop; } window.scrollTo(0, top); this._highlightEl(el); } - _observeShowAllActivity(showAllActivity) { + _observeShowAllActivity() { // We have to call render() such that the dom-repeat filter picks up the // change. this.$.messageRepeat.render(); @@ -266,7 +305,7 @@ class GrMessagesList extends KeyboardShortcutMixin( /** * Filter for the dom-repeat of combinedMessages. */ - _isMessageVisible(message) { + _isMessageVisible(message: CombinedMessage) { return this._showAllActivity || message.isImportant; } @@ -274,17 +313,26 @@ class GrMessagesList extends KeyboardShortcutMixin( * Merges change messages and reviewer updates into one array. Also processes * all messages and updates, aligns or massages some of the properties. */ - _computeCombinedMessages(messages, reviewerUpdates, changeComments) { - const params = [messages, reviewerUpdates, changeComments]; - if (params.some(o => o === undefined)) return []; + _computeCombinedMessages( + messages?: ChangeMessageInfo[], + reviewerUpdates?: FormattedReviewerUpdateInfo[], + changeComments?: ChangeComments + ) { + if ( + messages === undefined || + reviewerUpdates === undefined || + changeComments === undefined + ) + return []; let mi = 0; let ri = 0; - let combinedMessages = []; + let combinedMessages: CombinedMessage[] = []; let mDate; let rDate; for (let i = 0; i < messages.length; i++) { - messages[i]._index = i; + // TODO(TS): clone message instead and avoid API object mutation + (messages[i] as CombinedMessage)._index = i; } while (mi < messages.length || ri < reviewerUpdates.length) { @@ -310,7 +358,7 @@ class GrMessagesList extends KeyboardShortcutMixin( if (m.expanded === undefined) { m.expanded = false; } - m.commentThreads = computeThreads(m, combinedMessages, changeComments); + m.commentThreads = computeThreads(m, changeComments); m._revision_number = computeRevision(m, combinedMessages); m.tag = computeTag(m); }); @@ -323,7 +371,7 @@ class GrMessagesList extends KeyboardShortcutMixin( return combinedMessages; } - _updateExpandedStateOfAllMessages(exp) { + _updateExpandedStateOfAllMessages(exp: boolean) { if (this._combinedMessages) { for (let i = 0; i < this._combinedMessages.length; i++) { this._combinedMessages[i].expanded = exp; @@ -332,21 +380,24 @@ class GrMessagesList extends KeyboardShortcutMixin( } } - _computeExpandAllTitle(_expandAllState) { + _computeExpandAllTitle(_expandAllState?: string) { if (_expandAllState === ExpandAllState.COLLAPSE_ALL) { return this.createTitle( - Shortcut.COLLAPSE_ALL_MESSAGES, ShortcutSection.ACTIONS); + Shortcut.COLLAPSE_ALL_MESSAGES, + ShortcutSection.ACTIONS + ); } if (_expandAllState === ExpandAllState.EXPAND_ALL) { return this.createTitle( - Shortcut.EXPAND_ALL_MESSAGES, ShortcutSection.ACTIONS); + Shortcut.EXPAND_ALL_MESSAGES, + ShortcutSection.ACTIONS + ); } return ''; } - _highlightEl(el) { - const highlightedEls = - this.root.querySelectorAll('.highlighted'); + _highlightEl(el: HTMLElement) { + const highlightedEls = this.root!.querySelectorAll('.highlighted'); for (const highlightedEl of highlightedEls) { highlightedEl.classList.remove('highlighted'); } @@ -358,46 +409,49 @@ class GrMessagesList extends KeyboardShortcutMixin( el.classList.add('highlighted'); } - /** - * @param {boolean} expand - */ - handleExpandCollapse(expand) { - this._expandAllState = expand ? ExpandAllState.COLLAPSE_ALL + handleExpandCollapse(expand: boolean) { + this._expandAllState = expand + ? ExpandAllState.COLLAPSE_ALL : ExpandAllState.EXPAND_ALL; this._updateExpandedStateOfAllMessages(expand); } - _handleExpandCollapseTap(e) { + _handleExpandCollapseTap(e: Event) { e.preventDefault(); this.handleExpandCollapse( - this._expandAllState === ExpandAllState.EXPAND_ALL); + this._expandAllState === ExpandAllState.EXPAND_ALL + ); } - _handleAnchorClick(e) { + _handleAnchorClick(e: CustomEvent) { this.scrollToMessage(e.detail.id); } - _isVisibleShowAllActivityToggle(messages = []) { + _isVisibleShowAllActivityToggle(messages: CombinedMessage[] = []) { return messages.some(m => !m.isImportant); } - _computeHiddenEntriesCount(messages = []) { + _computeHiddenEntriesCount(messages: CombinedMessage[] = []) { return messages.filter(m => !m.isImportant).length; } /** * This method is for reporting stats only. */ - _combinedMessagesChanged(combinedMessages) { + _combinedMessagesChanged(combinedMessages?: CombinedMessage[]) { if (combinedMessages) { if (combinedMessages.length === 0) return; const tags = combinedMessages.map( - message => message.tag || message.type || - (message.comments ? 'comments' : 'none')); - const tagsCounted = tags.reduce((acc, val) => { - acc[val] = (acc[val] || 0) + 1; - return acc; - }, {all: combinedMessages.length}); + message => + message.tag || (message as FormattedReviewerUpdateInfo).type || 'none' + ); + const tagsCounted = tags.reduce( + (acc, val) => { + acc[val] = (acc[val] || 0) + 1; + return acc; + }, + {all: combinedMessages.length} as TagsCountReportInfo + ); this.reporting.reportInteraction('messages-count', tagsCounted); } } @@ -406,18 +460,22 @@ class GrMessagesList extends KeyboardShortcutMixin( * Compute a mapping from label name to objects representing the minimum and * maximum possible values for that label. */ - _computeLabelExtremes(labelRecord) { - const extremes = {}; + _computeLabelExtremes( + labelRecord: PolymerDeepPropertyChange< + LabelNameToInfoMap, + LabelNameToInfoMap + > + ) { + const extremes: {[lableName: string]: VotingRangeInfo} = {}; const labels = labelRecord.base; - if (!labels) { return extremes; } + if (!labels) { + return extremes; + } for (const key of Object.keys(labels)) { - // TODO(TS): Let's use label-util! - if (!labels[key] || !labels[key].values) { continue; } - const values = Object.keys(labels[key].values) - .map(v => parseInt(v, 10)); - values.sort((a, b) => a - b); - if (!values.length) { continue; } - extremes[key] = {min: values[0], max: values[values.length - 1]}; + const range = getVotingRange(labels[key]); + if (range) { + extremes[key] = range; + } } return extremes; } @@ -425,10 +483,13 @@ class GrMessagesList extends KeyboardShortcutMixin( /** * Work around a issue on iOS when clicking turns into double tap */ - _onTapShowAllActivityToggle(e) { + _onTapShowAllActivityToggle(e: Event) { e.preventDefault(); } } -customElements.define(GrMessagesList.is, - GrMessagesList); +declare global { + interface HTMLElementTagNameMap { + 'gr-messages-list': GrMessagesList; + } +} 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 9eec5c9..c9e448e 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 @@ -40,7 +40,7 @@ import { } from '../../../types/common'; import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; import {GrButton} from '../gr-button/gr-button'; -import {getVotingRange} from '../../../utils/label-util'; +import {getVotingRangeOrDefault} from '../../../utils/label-util'; export interface GrLabelInfo { $: { @@ -124,7 +124,7 @@ export class GrLabelInfo extends GestureEventListeners( const votes = (labelInfo.all || []).sort( (a, b) => (a.value || 0) - (b.value || 0) ); - const votingRange = getVotingRange(labelInfo); + const votingRange = getVotingRangeOrDefault(labelInfo); for (const label of votes) { if ( label.value && diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts index 39985cf..a3f9962 100644 --- a/polygerrit-ui/app/types/common.ts +++ b/polygerrit-ui/app/types/common.ts @@ -1135,7 +1135,7 @@ export interface UserConfigInfo { * The CommentInfo entity contains information about an inline comment. * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#comment-info */ -export interface CommentInfo extends CommentInput { +export interface CommentInfo { patch_set?: PatchSetNum; id: UrlEncodedCommentId; path?: string; diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts index 578ae11..3a2556f 100644 --- a/polygerrit-ui/app/utils/label-util.ts +++ b/polygerrit-ui/app/utils/label-util.ts @@ -24,16 +24,21 @@ import { // Name of the standard Code-Review label. export const CODE_REVIEW = 'Code-Review'; -export function getVotingRange(label?: LabelInfo): VotingRangeInfo { - if (!label || !isDetailedLabelInfo(label)) return {min: 0, max: 0}; +export function getVotingRange(label?: LabelInfo): VotingRangeInfo | undefined { + if (!label || !isDetailedLabelInfo(label)) return undefined; const values = Object.keys(label.values).map(v => parseInt(v, 10)); values.sort((a, b) => a - b); - if (!values.length) return {min: 0, max: 0}; + if (!values.length) return undefined; return {min: values[0], max: values[values.length - 1]}; } +export function getVotingRangeOrDefault(label?: LabelInfo): VotingRangeInfo { + const range = getVotingRange(label); + return range ? range : {min: 0, max: 0}; +} + export function getMaxAccounts(label?: LabelInfo): ApprovalInfo[] { if (!label || !isDetailedLabelInfo(label) || !label.all) return []; - const votingRange = getVotingRange(label); + const votingRange = getVotingRangeOrDefault(label); return label.all.filter(account => account.value === votingRange.max); } diff --git a/polygerrit-ui/app/utils/label-util_test.js b/polygerrit-ui/app/utils/label-util_test.js index eadd0a9..d6f7b3e 100644 --- a/polygerrit-ui/app/utils/label-util_test.js +++ b/polygerrit-ui/app/utils/label-util_test.js @@ -17,7 +17,9 @@ import '../test/common-test-setup-karma.js'; import { - getVotingRange, getMaxAccounts, + getVotingRange, + getVotingRangeOrDefault, + getMaxAccounts, } from './label-util.js'; const VALUES_1 = { @@ -39,12 +41,30 @@ suite('label-util', () => { const label = {values: VALUES_1}; const expectedRange = {min: -1, max: 1}; assert.deepEqual(getVotingRange(label), expectedRange); + assert.deepEqual(getVotingRangeOrDefault(label), expectedRange); }); test('getVotingRange -2 to +2', () => { const label = {values: VALUES_2}; const expectedRange = {min: -2, max: 2}; assert.deepEqual(getVotingRange(label), expectedRange); + assert.deepEqual(getVotingRangeOrDefault(label), expectedRange); + }); + + test('getVotingRange empty values', () => { + const label = { + values: {}, + }; + const expectedRange = {min: 0, max: 0}; + assert.isUndefined(getVotingRange(label)); + assert.deepEqual(getVotingRangeOrDefault(label), expectedRange); + }); + + test('getVotingRange no values', () => { + const label = {}; + const expectedRange = {min: 0, max: 0}; + assert.isUndefined(getVotingRange(label)); + assert.deepEqual(getVotingRangeOrDefault(label), expectedRange); }); test('getMaxAccounts', () => {