From 79d94952cafce2cc05726e4fe29a397346945c13 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 26 Feb 2024 14:45:58 +0000 Subject: [PATCH 01/10] refactor(typescript): migrate multiple tests and utils --- src/libs/OptionsListUtils.ts | 2 +- src/libs/ReportUtils.ts | 2 +- .../output/{markdown.js => markdown.ts} | 51 ++- tests/e2e/compare/types.ts | 57 ++++ tests/e2e/measure/math.ts | 11 +- tests/e2e/utils/logger.js | 10 +- ...erf-test.js => SidebarLinks.perf-test.tsx} | 22 +- .../{DateUtilsTest.js => DateUtilsTest.ts} | 55 ++-- ...stUtilsTest.js => OptionsListUtilsTest.ts} | 307 ++++++++++++------ ...{ReportUtilsTest.js => ReportUtilsTest.ts} | 230 +++++++------ 10 files changed, 467 insertions(+), 280 deletions(-) rename tests/e2e/compare/output/{markdown.js => markdown.ts} (59%) create mode 100644 tests/e2e/compare/types.ts rename tests/perf-test/{SidebarLinks.perf-test.js => SidebarLinks.perf-test.tsx} (85%) rename tests/unit/{DateUtilsTest.js => DateUtilsTest.ts} (85%) rename tests/unit/{OptionsListUtilsTest.js => OptionsListUtilsTest.ts} (89%) rename tests/unit/{ReportUtilsTest.js => ReportUtilsTest.ts} (80%) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 97b4fc0144c8..e4ec9da4dc12 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -2048,4 +2048,4 @@ export { getShareLogOptions, }; -export type {MemberForList, CategorySection, GetOptions}; +export type {MemberForList, CategorySection, GetOptions, PolicyTaxRateWithDefault, Tag}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ae6e02e70d29..d981309f0285 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -938,7 +938,7 @@ function filterReportsByPolicyIDAndMemberAccountIDs(reports: Report[], policyMem /** * Given an array of reports, return them sorted by the last read timestamp. */ -function sortReportsByLastRead(reports: Report[], reportMetadata: OnyxCollection): Array> { +function sortReportsByLastRead(reports: Array>, reportMetadata: OnyxCollection): Array> { return reports .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime)) .sort((a, b) => { diff --git a/tests/e2e/compare/output/markdown.js b/tests/e2e/compare/output/markdown.ts similarity index 59% rename from tests/e2e/compare/output/markdown.js rename to tests/e2e/compare/output/markdown.ts index 119830a5bb2c..07cf9897e570 100644 --- a/tests/e2e/compare/output/markdown.js +++ b/tests/e2e/compare/output/markdown.ts @@ -1,35 +1,34 @@ // From: https://raw.githubusercontent.com/callstack/reassure/main/packages/reassure-compare/src/output/markdown.ts import fs from 'node:fs/promises'; import path from 'path'; -import _ from 'underscore'; import * as Logger from '../../utils/logger'; +import type {AddedEntry, CompareEntry, CompareResult, PerformanceEntry, RemovedEntry} from '../types'; import * as format from './format'; import markdownTable from './markdownTable'; const tableHeader = ['Name', 'Duration']; -const collapsibleSection = (title, content) => `
\n${title}\n\n${content}\n
\n\n`; +const collapsibleSection = (title: string, content: string): string => `
\n${title}\n\n${content}\n
\n\n`; -const buildDurationDetails = (title, entry) => { +const buildDurationDetails = (title: string, entry: PerformanceEntry): string => { const relativeStdev = entry.stdev / entry.mean; - return _.filter( - [ - `**${title}**`, - `Mean: ${format.formatDuration(entry.mean)}`, - `Stdev: ${format.formatDuration(entry.stdev)} (${format.formatPercent(relativeStdev)})`, - entry.entries ? `Runs: ${entry.entries.join(' ')}` : '', - ], - Boolean, - ).join('
'); + return [ + `**${title}**`, + `Mean: ${format.formatDuration(entry.mean)}`, + `Stdev: ${format.formatDuration(entry.stdev)} (${format.formatPercent(relativeStdev)})`, + entry.entries ? `Runs: ${entry.entries.join(' ')}` : '', + ] + .filter(Boolean) + .join('
'); }; -const buildDurationDetailsEntry = (entry) => - _.filter(['baseline' in entry ? buildDurationDetails('Baseline', entry.baseline) : '', 'current' in entry ? buildDurationDetails('Current', entry.current) : ''], Boolean).join( - '

', - ); +const buildDurationDetailsEntry = (entry: CompareEntry | AddedEntry | RemovedEntry): string => + ['baseline' in entry ? buildDurationDetails('Baseline', entry.baseline) : '', 'current' in entry ? buildDurationDetails('Current', entry.current) : ''] + .filter(Boolean) + .join('

'); -const formatEntryDuration = (entry) => { +const formatEntryDuration = (entry: CompareEntry | AddedEntry | RemovedEntry): string => { if ('baseline' in entry && 'current' in entry) { return format.formatDurationDiffChange(entry); } @@ -42,39 +41,39 @@ const formatEntryDuration = (entry) => { return ''; }; -const buildDetailsTable = (entries) => { +const buildDetailsTable = (entries: Array): string => { if (!entries.length) { return ''; } - const rows = _.map(entries, (entry) => [entry.name, buildDurationDetailsEntry(entry)]); + const rows = entries.map((entry) => [entry.name, buildDurationDetailsEntry(entry)]); const content = markdownTable([tableHeader, ...rows]); return collapsibleSection('Show details', content); }; -const buildSummaryTable = (entries, collapse = false) => { +const buildSummaryTable = (entries: Array, collapse = false): string => { if (!entries.length) { return '_There are no entries_'; } - const rows = _.map(entries, (entry) => [entry.name, formatEntryDuration(entry)]); + const rows = entries.map((entry) => [entry.name, formatEntryDuration(entry)]); const content = markdownTable([tableHeader, ...rows]); return collapse ? collapsibleSection('Show entries', content) : content; }; -const buildMarkdown = (data) => { +const buildMarkdown = (data: CompareResult): string => { let result = '## Performance Comparison Report 📊'; - if (data.errors && data.errors.length) { + if (data.errors?.length) { result += '\n\n### Errors\n'; data.errors.forEach((message) => { result += ` 1. 🛑 ${message}\n`; }); } - if (data.warnings && data.warnings.length) { + if (data.warnings?.length) { result += '\n\n### Warnings\n'; data.warnings.forEach((message) => { result += ` 1. 🟡 ${message}\n`; @@ -92,7 +91,7 @@ const buildMarkdown = (data) => { return result; }; -const writeToFile = (filePath, content) => +const writeToFile = (filePath: string, content: string): Promise => fs .writeFile(filePath, content) .then(() => { @@ -106,7 +105,7 @@ const writeToFile = (filePath, content) => throw error; }); -const writeToMarkdown = (filePath, data) => { +const writeToMarkdown = (filePath: string, data: CompareResult): Promise => { const markdown = buildMarkdown(data); return writeToFile(filePath, markdown).catch((error) => { console.error(error); diff --git a/tests/e2e/compare/types.ts b/tests/e2e/compare/types.ts new file mode 100644 index 000000000000..e0be36716977 --- /dev/null +++ b/tests/e2e/compare/types.ts @@ -0,0 +1,57 @@ +type Entries = number[]; + +/** Metadata information for performance results. */ + +/** Entry in the performance results file. */ +type PerformanceEntry = { + /** Number of times the measurement test was run. */ + runs: number; + + /** Arithmetic average of measured render/execution durations for each run. */ + mean: number; + + /** Standard deviation of measured render/execution durations for each run. */ + stdev: number; + + /** Array of measured render/execution durations for each run. */ + entries: Entries; +}; + +/** + * Compare entry for tests that have both baseline and current entry + */ +type CompareEntry = { + name: string; + current: PerformanceEntry; + baseline: PerformanceEntry; + durationDiff: number; + relativeDurationDiff: number; +}; + +/** + * Compare entry for tests that have only current entry + */ +type AddedEntry = { + name: string; + current: PerformanceEntry; +}; + +/** + * Compare entry for tests that have only baseline entry + */ +type RemovedEntry = { + name: string; + baseline: PerformanceEntry; +}; + +/** Output of compare function. */ +type CompareResult = { + significance: CompareEntry[]; + meaningless: CompareEntry[]; + added: AddedEntry[]; + removed: RemovedEntry[]; + errors?: string[]; + warnings?: string[]; +}; + +export type {Entries, PerformanceEntry, CompareEntry, AddedEntry, RemovedEntry, CompareResult}; diff --git a/tests/e2e/measure/math.ts b/tests/e2e/measure/math.ts index e1c0cb981a0c..b5e303af7ea7 100644 --- a/tests/e2e/measure/math.ts +++ b/tests/e2e/measure/math.ts @@ -1,11 +1,4 @@ -type Entries = number[]; - -type Stats = { - mean: number; - stdev: number; - runs: number; - entries: Entries; -}; +import type {Entries, PerformanceEntry} from '../compare/types'; const filterOutliersViaIQR = (data: Entries): Entries => { let q1; @@ -35,7 +28,7 @@ const std = (arr: Entries): number => { return Math.sqrt(arr.map((i) => (i - avg) ** 2).reduce((a, b) => a + b) / arr.length); }; -const getStats = (entries: Entries): Stats => { +const getStats = (entries: Entries): PerformanceEntry => { const cleanedEntries = filterOutliersViaIQR(entries); const meanDuration = mean(cleanedEntries); const stdevDuration = std(cleanedEntries); diff --git a/tests/e2e/utils/logger.js b/tests/e2e/utils/logger.js index d0770b7aa8e4..6a39b4c10328 100644 --- a/tests/e2e/utils/logger.js +++ b/tests/e2e/utils/logger.js @@ -66,12 +66,4 @@ const error = (...args) => { log(...lines); }; -module.exports = { - log, - info, - warn, - note, - error, - success, - writeToLogFile, -}; +export {log, info, warn, note, error, success, writeToLogFile}; diff --git a/tests/perf-test/SidebarLinks.perf-test.js b/tests/perf-test/SidebarLinks.perf-test.tsx similarity index 85% rename from tests/perf-test/SidebarLinks.perf-test.js rename to tests/perf-test/SidebarLinks.perf-test.tsx index 0b10718fd0c4..c19a34517f45 100644 --- a/tests/perf-test/SidebarLinks.perf-test.js +++ b/tests/perf-test/SidebarLinks.perf-test.tsx @@ -1,23 +1,23 @@ import {fireEvent, screen} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; import {measurePerformance} from 'reassure'; -import _ from 'underscore'; -import CONST from '../../src/CONST'; -import ONYXKEYS from '../../src/ONYXKEYS'; -import variables from '../../src/styles/variables'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -jest.mock('../../src/libs/Permissions'); -jest.mock('../../src/hooks/usePermissions.ts'); -jest.mock('../../src/libs/Navigation/Navigation'); -jest.mock('../../src/components/Icon/Expensicons'); +jest.mock('@libs/Permissions'); +jest.mock('@hooks/usePermissions.ts'); +jest.mock('@libs/Navigation/Navigation'); +jest.mock('@components/Icon/Expensicons'); jest.mock('@react-navigation/native'); const getMockedReportsMap = (length = 100) => { - const mockReports = Array.from({length}, (__, i) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const mockReports = Array.from({length}, (_, i) => { const reportID = i + 1; const participants = [1, 2]; const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; @@ -26,7 +26,7 @@ const getMockedReportsMap = (length = 100) => { return {[reportKey]: report}; }); - return _.assign({}, ...mockReports); + return {...mockReports}; }; const mockedResponseMap = getMockedReportsMap(500); @@ -36,11 +36,9 @@ describe('SidebarLinks', () => { Onyx.init({ keys: ONYXKEYS, safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - registerStorageEventListener: () => {}, }); Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, diff --git a/tests/unit/DateUtilsTest.js b/tests/unit/DateUtilsTest.ts similarity index 85% rename from tests/unit/DateUtilsTest.js rename to tests/unit/DateUtilsTest.ts index a752eea1a990..8758050e6f62 100644 --- a/tests/unit/DateUtilsTest.js +++ b/tests/unit/DateUtilsTest.ts @@ -1,9 +1,11 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import {addDays, addMinutes, format, setHours, setMinutes, subDays, subHours, subMinutes, subSeconds} from 'date-fns'; import {format as tzFormat, utcToZonedTime} from 'date-fns-tz'; import Onyx from 'react-native-onyx'; -import CONST from '../../src/CONST'; -import DateUtils from '../../src/libs/DateUtils'; -import ONYXKEYS from '../../src/ONYXKEYS'; +import DateUtils from '@libs/DateUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {SelectedTimezone} from '@src/types/onyx/PersonalDetails'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const LOCALE = CONST.LOCALES.EN; @@ -14,13 +16,14 @@ describe('DateUtils', () => { keys: ONYXKEYS, initialKeyStates: { [ONYXKEYS.SESSION]: {accountID: 999}, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: {999: {timezone: {selected: UTC}}}, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: {'999': {accountID: 999, timezone: {selected: 'Europe/London'}}}, }, }); return waitForBatchedUpdates(); }); afterEach(() => { + jest.restoreAllMocks(); jest.useRealTimers(); Onyx.clear(); }); @@ -53,32 +56,35 @@ describe('DateUtils', () => { }); it('should fallback to current date when getLocalDateFromDatetime is failing', () => { - const localDate = DateUtils.getLocalDateFromDatetime(LOCALE, undefined, 'InvalidTimezone'); + const localDate = DateUtils.getLocalDateFromDatetime(LOCALE, undefined, 'InvalidTimezone' as SelectedTimezone); expect(localDate.getTime()).not.toBeNaN(); }); it('should return the date in calendar time when calling datetimeToCalendarTime', () => { - const today = setMinutes(setHours(new Date(), 14), 32); + const today = setMinutes(setHours(new Date(), 14), 32).toString(); expect(DateUtils.datetimeToCalendarTime(LOCALE, today)).toBe('Today at 2:32 PM'); - const tomorrow = addDays(setMinutes(setHours(new Date(), 14), 32), 1); + const tomorrow = addDays(setMinutes(setHours(new Date(), 14), 32), 1).toString(); expect(DateUtils.datetimeToCalendarTime(LOCALE, tomorrow)).toBe('Tomorrow at 2:32 PM'); - const yesterday = setMinutes(setHours(subDays(new Date(), 1), 7), 43); + const yesterday = setMinutes(setHours(subDays(new Date(), 1), 7), 43).toString(); expect(DateUtils.datetimeToCalendarTime(LOCALE, yesterday)).toBe('Yesterday at 7:43 AM'); - const date = setMinutes(setHours(new Date('2022-11-05'), 10), 17); + const date = setMinutes(setHours(new Date('2022-11-05'), 10), 17).toString(); expect(DateUtils.datetimeToCalendarTime(LOCALE, date)).toBe('Nov 5, 2022 at 10:17 AM'); - const todayLowercaseDate = setMinutes(setHours(new Date(), 14), 32); + const todayLowercaseDate = setMinutes(setHours(new Date(), 14), 32).toString(); expect(DateUtils.datetimeToCalendarTime(LOCALE, todayLowercaseDate, false, undefined, true)).toBe('today at 2:32 PM'); }); it('should update timezone if automatic and selected timezone do not match', () => { - Intl.DateTimeFormat = jest.fn(() => ({ - resolvedOptions: () => ({timeZone: 'America/Chicago'}), - })); - Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {999: {timezone: {selected: UTC, automatic: true}}}).then(() => { + jest.spyOn(Intl, 'DateTimeFormat').mockImplementation( + () => + ({ + resolvedOptions: () => ({timeZone: 'America/Chicago'}), + } as Intl.DateTimeFormat), + ); + Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {'999': {accountID: 999, timezone: {selected: 'Europe/London', automatic: true}}}).then(() => { const result = DateUtils.getCurrentTimezone(); expect(result).toEqual({ selected: 'America/Chicago', @@ -88,10 +94,13 @@ describe('DateUtils', () => { }); it('should not update timezone if automatic and selected timezone match', () => { - Intl.DateTimeFormat = jest.fn(() => ({ - resolvedOptions: () => ({timeZone: UTC}), - })); - Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {999: {timezone: {selected: UTC, automatic: true}}}).then(() => { + jest.spyOn(Intl, 'DateTimeFormat').mockImplementation( + () => + ({ + resolvedOptions: () => ({timeZone: UTC}), + } as Intl.DateTimeFormat), + ); + Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {'999': {accountID: 999, timezone: {selected: 'Europe/London', automatic: true}}}).then(() => { const result = DateUtils.getCurrentTimezone(); expect(result).toEqual({ selected: UTC, @@ -102,7 +111,7 @@ describe('DateUtils', () => { it('canUpdateTimezone should return true when lastUpdatedTimezoneTime is more than 5 minutes ago', () => { // Use fake timers to control the current time - jest.useFakeTimers('modern'); + jest.useFakeTimers(); jest.setSystemTime(addMinutes(new Date(), 6)); const isUpdateTimezoneAllowed = DateUtils.canUpdateTimezone(); expect(isUpdateTimezoneAllowed).toBe(true); @@ -110,20 +119,20 @@ describe('DateUtils', () => { it('canUpdateTimezone should return false when lastUpdatedTimezoneTime is less than 5 minutes ago', () => { // Use fake timers to control the current time - jest.useFakeTimers('modern'); + jest.useFakeTimers(); jest.setSystemTime(addMinutes(new Date(), 4)); const isUpdateTimezoneAllowed = DateUtils.canUpdateTimezone(); expect(isUpdateTimezoneAllowed).toBe(false); }); it('should return the date in calendar time when calling datetimeToRelative', () => { - const aFewSecondsAgo = subSeconds(new Date(), 10); + const aFewSecondsAgo = subSeconds(new Date(), 10).toString(); expect(DateUtils.datetimeToRelative(LOCALE, aFewSecondsAgo)).toBe('less than a minute ago'); - const aMinuteAgo = subMinutes(new Date(), 1); + const aMinuteAgo = subMinutes(new Date(), 1).toString(); expect(DateUtils.datetimeToRelative(LOCALE, aMinuteAgo)).toBe('1 minute ago'); - const anHourAgo = subHours(new Date(), 1); + const anHourAgo = subHours(new Date(), 1).toString(); expect(DateUtils.datetimeToRelative(LOCALE, anHourAgo)).toBe('about 1 hour ago'); }); diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.ts similarity index 89% rename from tests/unit/OptionsListUtilsTest.js rename to tests/unit/OptionsListUtilsTest.ts index 00f1307ab59f..07dabf143110 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.ts @@ -1,30 +1,32 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import CONST from '../../src/CONST'; -import * as OptionsListUtils from '../../src/libs/OptionsListUtils'; -import * as ReportUtils from '../../src/libs/ReportUtils'; -import ONYXKEYS from '../../src/ONYXKEYS'; +import CONST from '@src/CONST'; +import type {PolicyTaxRateWithDefault, Tag} from '@src/libs/OptionsListUtils'; +import * as OptionsListUtils from '@src/libs/OptionsListUtils'; +import * as ReportUtils from '@src/libs/ReportUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {PolicyCategories, Report} from '@src/types/onyx'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; describe('OptionsListUtils', () => { // Given a set of reports with both single participants and multiple participants some pinned and some not - const REPORTS = { - 1: { + const REPORTS: Record = { + '1': { lastReadTime: '2021-01-14 11:25:39.295', lastVisibleActionCreated: '2022-11-22 03:26:02.015', isPinned: false, - reportID: 1, + reportID: '1', participantAccountIDs: [2, 1], visibleChatMemberAccountIDs: [2, 1], reportName: 'Iron Man, Mister Fantastic', hasDraft: true, type: CONST.REPORT.TYPE.CHAT, }, - 2: { + '2': { lastReadTime: '2021-01-14 11:25:39.296', lastVisibleActionCreated: '2022-11-22 03:26:02.016', isPinned: false, - reportID: 2, + reportID: '2', participantAccountIDs: [3], visibleChatMemberAccountIDs: [3], reportName: 'Spider-Man', @@ -32,41 +34,41 @@ describe('OptionsListUtils', () => { }, // This is the only report we are pinning in this test - 3: { + '3': { lastReadTime: '2021-01-14 11:25:39.297', lastVisibleActionCreated: '2022-11-22 03:26:02.170', isPinned: true, - reportID: 3, + reportID: '3', participantAccountIDs: [1], visibleChatMemberAccountIDs: [1], reportName: 'Mister Fantastic', type: CONST.REPORT.TYPE.CHAT, }, - 4: { + '4': { lastReadTime: '2021-01-14 11:25:39.298', lastVisibleActionCreated: '2022-11-22 03:26:02.180', isPinned: false, - reportID: 4, + reportID: '4', participantAccountIDs: [4], visibleChatMemberAccountIDs: [4], reportName: 'Black Panther', type: CONST.REPORT.TYPE.CHAT, }, - 5: { + '5': { lastReadTime: '2021-01-14 11:25:39.299', lastVisibleActionCreated: '2022-11-22 03:26:02.019', isPinned: false, - reportID: 5, + reportID: '5', participantAccountIDs: [5], visibleChatMemberAccountIDs: [5], reportName: 'Invisible Woman', type: CONST.REPORT.TYPE.CHAT, }, - 6: { + '6': { lastReadTime: '2021-01-14 11:25:39.300', lastVisibleActionCreated: '2022-11-22 03:26:02.020', isPinned: false, - reportID: 6, + reportID: '6', participantAccountIDs: [6], visibleChatMemberAccountIDs: [6], reportName: 'Thor', @@ -74,11 +76,11 @@ describe('OptionsListUtils', () => { }, // Note: This report has the largest lastVisibleActionCreated - 7: { + '7': { lastReadTime: '2021-01-14 11:25:39.301', lastVisibleActionCreated: '2022-11-22 03:26:03.999', isPinned: false, - reportID: 7, + reportID: '7', participantAccountIDs: [7], visibleChatMemberAccountIDs: [7], reportName: 'Captain America', @@ -86,11 +88,11 @@ describe('OptionsListUtils', () => { }, // Note: This report has no lastVisibleActionCreated - 8: { + '8': { lastReadTime: '2021-01-14 11:25:39.301', lastVisibleActionCreated: '2022-11-22 03:26:02.000', isPinned: false, - reportID: 8, + reportID: '8', participantAccountIDs: [12], visibleChatMemberAccountIDs: [12], reportName: 'Silver Surfer', @@ -98,23 +100,23 @@ describe('OptionsListUtils', () => { }, // Note: This report has an IOU - 9: { + '9': { lastReadTime: '2021-01-14 11:25:39.302', lastVisibleActionCreated: '2022-11-22 03:26:02.998', isPinned: false, - reportID: 9, + reportID: '9', participantAccountIDs: [8], visibleChatMemberAccountIDs: [8], reportName: 'Mister Sinister', - iouReportID: 100, + iouReportID: '100', type: CONST.REPORT.TYPE.CHAT, }, // This report is an archived room – it does not have a name and instead falls back on oldPolicyName - 10: { + '10': { lastReadTime: '2021-01-14 11:25:39.200', lastVisibleActionCreated: '2022-11-22 03:26:02.001', - reportID: 10, + reportID: '10', isPinned: false, participantAccountIDs: [2, 7], visibleChatMemberAccountIDs: [2, 7], @@ -133,69 +135,79 @@ describe('OptionsListUtils', () => { // And a set of personalDetails some with existing reports and some without const PERSONAL_DETAILS = { // These exist in our reports - 1: { + '1': { accountID: 1, displayName: 'Mister Fantastic', login: 'reedrichards@expensify.com', isSelected: true, + reportID: '1', }, - 2: { + '2': { accountID: 2, displayName: 'Iron Man', login: 'tonystark@expensify.com', + reportID: '1', }, - 3: { + '3': { accountID: 3, displayName: 'Spider-Man', login: 'peterparker@expensify.com', + reportID: '1', }, - 4: { + '4': { accountID: 4, displayName: 'Black Panther', login: 'tchalla@expensify.com', + reportID: '1', }, - 5: { + '5': { accountID: 5, displayName: 'Invisible Woman', login: 'suestorm@expensify.com', + reportID: '1', }, - 6: { + '6': { accountID: 6, displayName: 'Thor', login: 'thor@expensify.com', + reportID: '1', }, - 7: { + '7': { accountID: 7, displayName: 'Captain America', login: 'steverogers@expensify.com', + reportID: '1', }, - 8: { + '8': { accountID: 8, displayName: 'Mr Sinister', login: 'mistersinister@marauders.com', + reportID: '1', }, // These do not exist in reports at all - 9: { + '9': { accountID: 9, displayName: 'Black Widow', login: 'natasharomanoff@expensify.com', + reportID: '', }, - 10: { + '10': { accountID: 10, displayName: 'The Incredible Hulk', login: 'brucebanner@expensify.com', + reportID: '', }, }; const REPORTS_WITH_CONCIERGE = { ...REPORTS, - 11: { + '11': { lastReadTime: '2021-01-14 11:25:39.302', lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, - reportID: 11, + reportID: '11', participantAccountIDs: [999], visibleChatMemberAccountIDs: [999], reportName: 'Concierge', @@ -205,11 +217,11 @@ describe('OptionsListUtils', () => { const REPORTS_WITH_CHRONOS = { ...REPORTS, - 12: { + '12': { lastReadTime: '2021-01-14 11:25:39.302', lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, - reportID: 12, + reportID: '12', participantAccountIDs: [1000], visibleChatMemberAccountIDs: [1000], reportName: 'Chronos', @@ -219,11 +231,11 @@ describe('OptionsListUtils', () => { const REPORTS_WITH_RECEIPTS = { ...REPORTS, - 13: { + '13': { lastReadTime: '2021-01-14 11:25:39.302', lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, - reportID: 13, + reportID: '13', participantAccountIDs: [1001], visibleChatMemberAccountIDs: [1001], reportName: 'Receipts', @@ -233,11 +245,11 @@ describe('OptionsListUtils', () => { const REPORTS_WITH_WORKSPACE_ROOMS = { ...REPORTS, - 14: { + '14': { lastReadTime: '2021-01-14 11:25:39.302', lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, - reportID: 14, + reportID: '14', participantAccountIDs: [1, 10, 3], visibleChatMemberAccountIDs: [1, 10, 3], reportName: '', @@ -252,7 +264,7 @@ describe('OptionsListUtils', () => { const PERSONAL_DETAILS_WITH_CONCIERGE = { ...PERSONAL_DETAILS, - 999: { + '999': { accountID: 999, displayName: 'Concierge', login: 'concierge@expensify.com', @@ -262,7 +274,7 @@ describe('OptionsListUtils', () => { const PERSONAL_DETAILS_WITH_CHRONOS = { ...PERSONAL_DETAILS, - 1000: { + '1000': { accountID: 1000, displayName: 'Chronos', login: 'chronos@expensify.com', @@ -272,7 +284,7 @@ describe('OptionsListUtils', () => { const PERSONAL_DETAILS_WITH_RECEIPTS = { ...PERSONAL_DETAILS, - 1001: { + '1001': { accountID: 1001, displayName: 'Receipts', login: 'receipts@expensify.com', @@ -282,7 +294,7 @@ describe('OptionsListUtils', () => { const PERSONAL_DETAILS_WITH_PERIODS = { ...PERSONAL_DETAILS, - 1002: { + '1002': { accountID: 1002, displayName: 'The Flash', login: 'barry.allen@expensify.com', @@ -290,9 +302,14 @@ describe('OptionsListUtils', () => { }; const POLICY = { - policyID: 'ABC123', + id: 'ABC123', name: 'Hero Policy', - }; + role: 'user', + type: 'free', + owner: '', + outputCurrency: '', + isPolicyExpenseChatEnabled: false, + } as const; // Set the currently logged in user, report data, and personal details beforeAll(() => { @@ -300,11 +317,12 @@ describe('OptionsListUtils', () => { keys: ONYXKEYS, initialKeyStates: { [ONYXKEYS.SESSION]: {accountID: 2, email: 'tonystark@expensify.com'}, - [`${ONYXKEYS.COLLECTION.REPORT}100`]: { + [`${ONYXKEYS.COLLECTION.REPORT}100` as const]: { + reportID: '', ownerAccountID: 8, - total: '1000', + total: 1000, }, - [`${ONYXKEYS.COLLECTION.POLICY}${POLICY.policyID}`]: POLICY, + [`${ONYXKEYS.COLLECTION.POLICY}${POLICY.id}` as const]: POLICY, }, }); Onyx.registerLogger(() => {}); @@ -319,7 +337,7 @@ describe('OptionsListUtils', () => { expect(results.personalDetails.length).toBe(2); // Then all of the reports should be shown including the archived rooms. - expect(results.recentReports.length).toBe(_.size(REPORTS)); + expect(results.recentReports.length).toBe(Object.values(REPORTS).length); // When we filter again but provide a searchValue results = OptionsListUtils.getSearchOptions(REPORTS, PERSONAL_DETAILS, 'spider'); @@ -360,7 +378,7 @@ describe('OptionsListUtils', () => { // We should expect all personalDetails to be returned, // minus the currently logged in user and recent reports count - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS) - 1 - MAX_RECENT_REPORTS); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS).length - 1 - MAX_RECENT_REPORTS); // We should expect personal details sorted alphabetically expect(results.personalDetails[0].text).toBe('Black Widow'); @@ -369,11 +387,11 @@ describe('OptionsListUtils', () => { expect(results.personalDetails[3].text).toBe('The Incredible Hulk'); // Then the result which has an existing report should also have the reportID attached - const personalDetailWithExistingReport = _.find(results.personalDetails, (personalDetail) => personalDetail.login === 'peterparker@expensify.com'); - expect(personalDetailWithExistingReport.reportID).toBe(2); + const personalDetailWithExistingReport = results.personalDetails.find((personalDetail) => personalDetail.login === 'peterparker@expensify.com'); + expect(personalDetailWithExistingReport?.reportID).toBe('2'); // When we only pass personal details - results = OptionsListUtils.getFilteredOptions([], PERSONAL_DETAILS, [], ''); + results = OptionsListUtils.getFilteredOptions({}, PERSONAL_DETAILS, [], ''); // We should expect personal details sorted alphabetically expect(results.personalDetails[0].text).toBe('Black Panther'); @@ -414,28 +432,28 @@ describe('OptionsListUtils', () => { // Concierge is included in the results by default. We should expect all the personalDetails to show // (minus the 5 that are already showing and the currently logged in user) - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_CONCIERGE) - 1 - MAX_RECENT_REPORTS); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS_WITH_CONCIERGE).length - 1 - MAX_RECENT_REPORTS); expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Concierge from the results results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE, [], '', [], [CONST.EMAIL.CONCIERGE]); // All the personalDetails should be returned minus the currently logged in user and Concierge - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_CONCIERGE) - 2 - MAX_RECENT_REPORTS); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS_WITH_CONCIERGE).length - 2 - MAX_RECENT_REPORTS); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Chronos from the results results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_CHRONOS, PERSONAL_DETAILS_WITH_CHRONOS, [], '', [], [CONST.EMAIL.CHRONOS]); // All the personalDetails should be returned minus the currently logged in user and Concierge - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_CHRONOS) - 2 - MAX_RECENT_REPORTS); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS_WITH_CHRONOS).length - 2 - MAX_RECENT_REPORTS); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); // Test by excluding Receipts from the results results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_RECEIPTS, PERSONAL_DETAILS_WITH_RECEIPTS, [], '', [], [CONST.EMAIL.RECEIPTS]); // All the personalDetails should be returned minus the currently logged in user and Concierge - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_RECEIPTS) - 2 - MAX_RECENT_REPORTS); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS_WITH_RECEIPTS).length - 2 - MAX_RECENT_REPORTS); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); }); @@ -448,7 +466,7 @@ describe('OptionsListUtils', () => { // And we should expect all the personalDetails to show (minus the 5 that are already // showing and the currently logged in user) - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS) - 6); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS).length - 6); // We should expect personal details sorted alphabetically expect(results.personalDetails[0].text).toBe('Black Widow'); @@ -457,8 +475,8 @@ describe('OptionsListUtils', () => { expect(results.personalDetails[3].text).toBe('The Incredible Hulk'); // And none of our personalDetails should include any of the users with recent reports - const reportLogins = _.map(results.recentReports, (reportOption) => reportOption.login); - const personalDetailsOverlapWithReports = _.every(results.personalDetails, (personalDetailOption) => _.contains(reportLogins, personalDetailOption.login)); + const reportLogins = results.recentReports.map((reportOption) => reportOption.login); + const personalDetailsOverlapWithReports = results.personalDetails.every((personalDetailOption) => reportLogins.includes(personalDetailOption.login)); expect(personalDetailsOverlapWithReports).toBe(false); // When we search for an option that is only in a personalDetail with no existing report @@ -487,15 +505,15 @@ describe('OptionsListUtils', () => { // Then one of our older report options (not in our five most recent) should appear in the personalDetails // but not in recentReports - expect(_.every(results.recentReports, (option) => option.login !== 'peterparker@expensify.com')).toBe(true); - expect(_.every(results.personalDetails, (option) => option.login !== 'peterparker@expensify.com')).toBe(false); + expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); + expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(false); // When we provide a "selected" option to getFilteredOptions() results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '', [{login: 'peterparker@expensify.com'}]); // Then the option should not appear anywhere in either list - expect(_.every(results.recentReports, (option) => option.login !== 'peterparker@expensify.com')).toBe(true); - expect(_.every(results.personalDetails, (option) => option.login !== 'peterparker@expensify.com')).toBe(true); + expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); + expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); // When we add a search term for which no options exist and the searchValue itself // is not a potential email or phone @@ -531,7 +549,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports.length).toBe(0); expect(results.personalDetails.length).toBe(0); expect(results.userToInvite).not.toBe(null); - expect(results.userToInvite.login).toBe('+15005550006'); + expect(results.userToInvite?.login).toBe('+15005550006'); // When we add a search term for which no options exist and the searchValue itself // is a potential phone number with country code added @@ -542,7 +560,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports.length).toBe(0); expect(results.personalDetails.length).toBe(0); expect(results.userToInvite).not.toBe(null); - expect(results.userToInvite.login).toBe('+15005550006'); + expect(results.userToInvite?.login).toBe('+15005550006'); // When we add a search term for which no options exist and the searchValue itself // is a potential phone number with special characters added @@ -553,7 +571,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports.length).toBe(0); expect(results.personalDetails.length).toBe(0); expect(results.userToInvite).not.toBe(null); - expect(results.userToInvite.login).toBe('+18003243233'); + expect(results.userToInvite?.login).toBe('+18003243233'); // When we use a search term for contact number that contains alphabet characters results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '998243aaaa'); @@ -568,7 +586,7 @@ describe('OptionsListUtils', () => { // Concierge is included in the results by default. We should expect all the personalDetails to show // (minus the 5 that are already showing and the currently logged in user) - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_CONCIERGE) - 6); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS_WITH_CONCIERGE).length - 6); expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Concierge from the results @@ -576,7 +594,7 @@ describe('OptionsListUtils', () => { // We should expect all the personalDetails to show (minus the 5 that are already showing, // the currently logged in user and Concierge) - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_CONCIERGE) - 7); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS_WITH_CONCIERGE).length - 7); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); @@ -585,7 +603,7 @@ describe('OptionsListUtils', () => { // We should expect all the personalDetails to show (minus the 5 that are already showing, // the currently logged in user and Concierge) - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_CHRONOS) - 7); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS_WITH_CHRONOS).length - 7); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); @@ -594,26 +612,25 @@ describe('OptionsListUtils', () => { // We should expect all the personalDetails to show (minus the 5 that are already showing, // the currently logged in user and Concierge) - expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_RECEIPTS) - 7); + expect(results.personalDetails.length).toBe(Object.values(PERSONAL_DETAILS_WITH_RECEIPTS).length - 7); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); }); it('getShareDestinationsOptions()', () => { // Filter current REPORTS as we do in the component, before getting share destination options - const filteredReports = {}; - _.keys(REPORTS).forEach((reportKey) => { - if (!ReportUtils.canUserPerformWriteAction(REPORTS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS[reportKey])) { - return; + const filteredReports = Object.entries(REPORTS).reduce>((reports, [reportKey, report]) => { + if (!ReportUtils.canUserPerformWriteAction(report) || ReportUtils.isExpensifyOnlyParticipantInReport(report)) { + return reports; } - filteredReports[reportKey] = REPORTS[reportKey]; - }); + return {...reports, [reportKey]: report}; + }, {}); // When we pass an empty search value let results = OptionsListUtils.getShareDestinationOptions(filteredReports, PERSONAL_DETAILS, [], ''); // Then we should expect all the recent reports to show but exclude the archived rooms - expect(results.recentReports.length).toBe(_.size(REPORTS) - 1); + expect(results.recentReports.length).toBe(Object.values(REPORTS).length - 1); // When we pass a search value that doesn't match the group chat name results = OptionsListUtils.getShareDestinationOptions(filteredReports, PERSONAL_DETAILS, [], 'mutants'); @@ -628,20 +645,19 @@ describe('OptionsListUtils', () => { expect(results.recentReports.length).toBe(1); // Filter current REPORTS_WITH_WORKSPACE_ROOMS as we do in the component, before getting share destination options - const filteredReportsWithWorkspaceRooms = {}; - _.keys(REPORTS_WITH_WORKSPACE_ROOMS).forEach((reportKey) => { - if (!ReportUtils.canUserPerformWriteAction(REPORTS_WITH_WORKSPACE_ROOMS[reportKey]) || ReportUtils.isExpensifyOnlyParticipantInReport(REPORTS_WITH_WORKSPACE_ROOMS[reportKey])) { - return; + const filteredReportsWithWorkspaceRooms = Object.entries(REPORTS_WITH_WORKSPACE_ROOMS).reduce>((reports, [reportKey, report]) => { + if (!ReportUtils.canUserPerformWriteAction(report) || ReportUtils.isExpensifyOnlyParticipantInReport(report)) { + return reports; } - filteredReportsWithWorkspaceRooms[reportKey] = REPORTS_WITH_WORKSPACE_ROOMS[reportKey]; - }); + return {...reports, [reportKey]: report}; + }, {}); // When we also have a policy to return rooms in the results results = OptionsListUtils.getShareDestinationOptions(filteredReportsWithWorkspaceRooms, PERSONAL_DETAILS, [], ''); // Then we should expect the DMS, the group chats and the workspace room to show // We should expect all the recent reports to show, excluding the archived rooms - expect(results.recentReports.length).toBe(_.size(REPORTS_WITH_WORKSPACE_ROOMS) - 1); + expect(results.recentReports.length).toBe(Object.values(REPORTS_WITH_WORKSPACE_ROOMS).length - 1); // When we search for a workspace room results = OptionsListUtils.getShareDestinationOptions(filteredReportsWithWorkspaceRooms, PERSONAL_DETAILS, [], 'Avengers Room'); @@ -691,22 +707,38 @@ describe('OptionsListUtils', () => { enabled: true, }, ]; - const smallCategoriesList = { + const smallCategoriesList: PolicyCategories = { Taxi: { enabled: false, name: 'Taxi', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, Restaurant: { enabled: true, name: 'Restaurant', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, Food: { enabled: true, name: 'Food', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Food: Meat': { enabled: true, name: 'Food: Meat', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, }; const smallResultList = [ @@ -775,62 +807,118 @@ describe('OptionsListUtils', () => { data: [], }, ]; - const largeCategoriesList = { + const largeCategoriesList: PolicyCategories = { Taxi: { enabled: false, name: 'Taxi', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, Restaurant: { enabled: true, name: 'Restaurant', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, Food: { enabled: true, name: 'Food', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Food: Meat': { enabled: true, name: 'Food: Meat', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Food: Milk': { enabled: true, name: 'Food: Milk', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Food: Vegetables': { enabled: false, name: 'Food: Vegetables', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Cars: Audi': { enabled: true, name: 'Cars: Audi', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Cars: BMW': { enabled: false, name: 'Cars: BMW', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Cars: Mercedes-Benz': { enabled: true, name: 'Cars: Mercedes-Benz', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, Medical: { enabled: false, name: 'Medical', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Travel: Meals': { enabled: true, name: 'Travel: Meals', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Travel: Meals: Breakfast': { enabled: true, name: 'Travel: Meals: Breakfast', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Travel: Meals: Dinner': { enabled: false, name: 'Travel: Meals: Dinner', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, 'Travel: Meals: Lunch': { enabled: true, name: 'Travel: Meals: Lunch', + areCommentsRequired: false, + 'GL Code': '', + externalID: '', + origin: '', }, }; const largeResultList = [ @@ -1089,22 +1177,26 @@ describe('OptionsListUtils', () => { name: 'Medical', }, ]; - const smallTagsList = { + const smallTagsList: Record = { Engineering: { enabled: false, name: 'Engineering', + accountID: null, }, Medical: { enabled: true, name: 'Medical', + accountID: null, }, Accounting: { enabled: true, name: 'Accounting', + accountID: null, }, HR: { enabled: true, name: 'HR', + accountID: null, }, }; const smallResultList = [ @@ -1162,50 +1254,61 @@ describe('OptionsListUtils', () => { data: [], }, ]; - const largeTagsList = { + const largeTagsList: Record = { Engineering: { enabled: false, name: 'Engineering', + accountID: null, }, Medical: { enabled: true, name: 'Medical', + accountID: null, }, Accounting: { enabled: true, name: 'Accounting', + accountID: null, }, HR: { enabled: true, name: 'HR', + accountID: null, }, Food: { enabled: true, name: 'Food', + accountID: null, }, Traveling: { enabled: false, name: 'Traveling', + accountID: null, }, Cleaning: { enabled: true, name: 'Cleaning', + accountID: null, }, Software: { enabled: true, name: 'Software', + accountID: null, }, OfficeSupplies: { enabled: false, name: 'Office Supplies', + accountID: null, }, Taxes: { enabled: true, name: 'Taxes', + accountID: null, }, Benefits: { enabled: true, name: 'Benefits', + accountID: null, }, }; const largeResultList = [ @@ -2063,7 +2166,7 @@ describe('OptionsListUtils', () => { const emptySearch = ''; const wrongSearch = 'bla bla'; - const policyTaxRatesWithDefault = { + const policyTaxRatesWithDefault: PolicyTaxRateWithDefault = { name: 'Tax', defaultExternalID: 'CODE1', defaultValue: '0%', @@ -2072,14 +2175,20 @@ describe('OptionsListUtils', () => { CODE2: { name: 'Tax rate 2', value: '3%', + code: '', + modifiedName: '', }, CODE3: { name: 'Tax option 3', value: '5%', + code: '', + modifiedName: '', }, CODE1: { name: 'Tax exempt 1', value: '0%', + code: '', + modifiedName: '', }, }, }; @@ -2201,7 +2310,7 @@ describe('OptionsListUtils', () => { }); it('formatMemberForList()', () => { - const formattedMembers = _.map(PERSONAL_DETAILS, (personalDetail) => OptionsListUtils.formatMemberForList(personalDetail)); + const formattedMembers = Object.values(PERSONAL_DETAILS).map((personalDetail) => OptionsListUtils.formatMemberForList(personalDetail)); // We're only formatting items inside the array, so the order should be the same as the original PERSONAL_DETAILS array expect(formattedMembers[0].text).toBe('Mister Fantastic'); @@ -2212,9 +2321,9 @@ describe('OptionsListUtils', () => { expect(formattedMembers[0].isSelected).toBe(true); // And all the others to be unselected - expect(_.every(formattedMembers.slice(1), (personalDetail) => !personalDetail.isSelected)).toBe(true); + expect(formattedMembers.slice(1).every((personalDetail) => !personalDetail.isSelected)).toBe(true); // `isDisabled` is always false - expect(_.every(formattedMembers, (personalDetail) => !personalDetail.isDisabled)).toBe(true); + expect(formattedMembers.every((personalDetail) => !personalDetail.isDisabled)).toBe(true); }); }); diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.ts similarity index 80% rename from tests/unit/ReportUtilsTest.js rename to tests/unit/ReportUtilsTest.ts index a5b0a5d3c151..b8375700304c 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.ts @@ -1,52 +1,59 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import CONST from '../../src/CONST'; -import * as ReportUtils from '../../src/libs/ReportUtils'; -import ONYXKEYS from '../../src/ONYXKEYS'; +import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; // Be sure to include the mocked permissions library or else the beta tests won't work -jest.mock('../../src/libs/Permissions'); +jest.mock('@libs/Permissions'); const currentUserEmail = 'bjorn@vikings.net'; const currentUserAccountID = 5; const participantsPersonalDetails = { - 1: { + '1': { accountID: 1, displayName: 'Ragnar Lothbrok', firstName: 'Ragnar', login: 'ragnar@vikings.net', }, - 2: { + '2': { accountID: 2, login: 'floki@vikings.net', displayName: 'floki@vikings.net', }, - 3: { + '3': { accountID: 3, displayName: 'Lagertha Lothbrok', firstName: 'Lagertha', login: 'lagertha@vikings.net', pronouns: 'She/her', }, - 4: { + '4': { accountID: 4, login: '+18332403627@expensify.sms', displayName: '(833) 240-3627', }, - 5: { + '5': { accountID: 5, displayName: 'Lagertha Lothbrok', firstName: 'Lagertha', login: 'lagertha2@vikings.net', pronouns: 'She/her', }, -}; +} as const; + const policy = { - policyID: 1, + id: '1', name: 'Vikings Policy', -}; + role: 'user', + type: 'free', + owner: '', + outputCurrency: '', + isPolicyExpenseChatEnabled: false, +} as const; Onyx.init({keys: ONYXKEYS}); @@ -56,7 +63,7 @@ describe('ReportUtils', () => { [ONYXKEYS.PERSONAL_DETAILS_LIST]: participantsPersonalDetails, [ONYXKEYS.SESSION]: {email: currentUserEmail, accountID: currentUserAccountID}, [ONYXKEYS.COUNTRY_CODE]: 1, - [`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy, + [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}` as const]: policy, }); return waitForBatchedUpdates(); }); @@ -106,6 +113,7 @@ describe('ReportUtils', () => { test('with displayName', () => { expect( ReportUtils.getReportName({ + reportID: '', participantAccountIDs: [currentUserAccountID, 1], }), ).toBe('Ragnar Lothbrok'); @@ -114,6 +122,7 @@ describe('ReportUtils', () => { test('no displayName', () => { expect( ReportUtils.getReportName({ + reportID: '', participantAccountIDs: [currentUserAccountID, 2], }), ).toBe('floki@vikings.net'); @@ -122,6 +131,7 @@ describe('ReportUtils', () => { test('SMS', () => { expect( ReportUtils.getReportName({ + reportID: '', participantAccountIDs: [currentUserAccountID, 4], }), ).toBe('(833) 240-3627'); @@ -131,6 +141,7 @@ describe('ReportUtils', () => { test('Group DM', () => { expect( ReportUtils.getReportName({ + reportID: '', participantAccountIDs: [currentUserAccountID, 1, 2, 3, 4], }), ).toBe('Ragnar, floki@vikings.net, Lagertha, (833) 240-3627'); @@ -138,6 +149,7 @@ describe('ReportUtils', () => { describe('Default Policy Room', () => { const baseAdminsRoom = { + reportID: '', chatType: CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, reportName: '#admins', }; @@ -161,6 +173,7 @@ describe('ReportUtils', () => { describe('User-Created Policy Room', () => { const baseUserCreatedRoom = { + reportID: '', chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, reportName: '#VikingsChat', }; @@ -187,8 +200,9 @@ describe('ReportUtils', () => { test('as member', () => { expect( ReportUtils.getReportName({ + reportID: '', chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - policyID: policy.policyID, + policyID: policy.id, isOwnPolicyExpenseChat: true, ownerAccountID: 1, }), @@ -198,8 +212,9 @@ describe('ReportUtils', () => { test('as admin', () => { expect( ReportUtils.getReportName({ + reportID: '', chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - policyID: policy.policyID, + policyID: policy.id, isOwnPolicyExpenseChat: false, ownerAccountID: 1, }), @@ -209,9 +224,10 @@ describe('ReportUtils', () => { describe('Archived', () => { const baseArchivedPolicyExpenseChat = { + reportID: '', chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, ownerAccountID: 1, - policyID: policy.policyID, + policyID: policy.id, oldPolicyName: policy.name, statusNum: CONST.REPORT.STATUS_NUM.CLOSED, stateNum: CONST.REPORT.STATE_NUM.APPROVED, @@ -248,18 +264,18 @@ describe('ReportUtils', () => { describe('requiresAttentionFromCurrentUser', () => { it('returns false when there is no report', () => { - expect(ReportUtils.requiresAttentionFromCurrentUser()).toBe(false); + expect(ReportUtils.requiresAttentionFromCurrentUser(null)).toBe(false); }); it('returns false when the matched IOU report does not have an owner accountID', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), ownerAccountID: undefined, }; expect(ReportUtils.requiresAttentionFromCurrentUser(report)).toBe(false); }); it('returns false when the linked iou report has an oustanding IOU', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), iouReportID: '1', }; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, { @@ -271,7 +287,7 @@ describe('ReportUtils', () => { }); it('returns false when the report has no outstanding IOU but is waiting for a bank account and the logged user is the report owner', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), ownerAccountID: currentUserAccountID, isWaitingOnBankAccount: true, }; @@ -279,7 +295,7 @@ describe('ReportUtils', () => { }); it('returns false when the report has outstanding IOU and is not waiting for a bank account and the logged user is the report owner', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), ownerAccountID: currentUserAccountID, isWaitingOnBankAccount: false, }; @@ -287,7 +303,7 @@ describe('ReportUtils', () => { }); it('returns false when the report has no oustanding IOU but is waiting for a bank account and the logged user is not the report owner', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), ownerAccountID: 97, isWaitingOnBankAccount: true, }; @@ -295,14 +311,14 @@ describe('ReportUtils', () => { }); it('returns true when the report has an unread mention', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), isUnreadWithMention: true, }; expect(ReportUtils.requiresAttentionFromCurrentUser(report)).toBe(true); }); it('returns true when the report is an outstanding task', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.TASK, managerID: currentUserAccountID, isUnreadWithMention: false, @@ -313,7 +329,7 @@ describe('ReportUtils', () => { }); it('returns true when the report has oustanding child request', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), ownerAccountID: 99, hasOutstandingChildRequest: true, isWaitingOnBankAccount: false, @@ -323,7 +339,7 @@ describe('ReportUtils', () => { }); describe('getMoneyRequestOptions', () => { - const participantsAccountIDs = _.keys(participantsPersonalDetails); + const participantsAccountIDs = Object.keys(participantsPersonalDetails).map(Number); beforeAll(() => { Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { @@ -338,8 +354,8 @@ describe('ReportUtils', () => { describe('return empty iou options if', () => { it('participants aray contains excluded expensify iou emails', () => { - const allEmpty = _.every(CONST.EXPENSIFY_ACCOUNT_IDS, (accountID) => { - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions({}, {}, [currentUserAccountID, accountID]); + const allEmpty = CONST.EXPENSIFY_ACCOUNT_IDS.every((accountID) => { + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(null, null, [currentUserAccountID, accountID]); return moneyRequestOptions.length === 0; }); expect(allEmpty).toBe(true); @@ -347,51 +363,51 @@ describe('ReportUtils', () => { it('it is a room with no participants except self', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); it('its not your policy expense chat', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: false, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); it('its paid IOU report', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.IOU, statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); it('its approved Expense report', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.EXPENSE, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); it('its paid Expense report', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.EXPENSE, statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); @@ -401,11 +417,11 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: false, }).then(() => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), parentReportID: '100', type: CONST.REPORT.TYPE.EXPENSE, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); }); @@ -417,7 +433,7 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: true, }).then(() => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.EXPENSE, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, @@ -425,7 +441,13 @@ describe('ReportUtils', () => { }; const paidPolicy = { type: CONST.POLICY.TYPE.TEAM, - }; + id: '', + name: '', + role: 'user', + owner: '', + outputCurrency: '', + isPolicyExpenseChatEnabled: false, + } as const; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, paidPolicy, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(0); }); @@ -434,47 +456,49 @@ describe('ReportUtils', () => { describe('return only iou split option if', () => { it('it is a chat room with more than one participant', () => { - const onlyHaveSplitOption = _.every( - [CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, CONST.REPORT.CHAT_TYPE.DOMAIN_ALL, CONST.REPORT.CHAT_TYPE.POLICY_ROOM], - (chatType) => { - const report = { - ...LHNTestUtils.getFakeReport(), - chatType, - }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); - return moneyRequestOptions.length === 1 && moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT); - }, - ); + const onlyHaveSplitOption = [ + CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, + CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, + CONST.REPORT.CHAT_TYPE.DOMAIN_ALL, + CONST.REPORT.CHAT_TYPE.POLICY_ROOM, + ].every((chatType) => { + const report = { + ...(LHNTestUtils.getFakeReport() as Report), + chatType, + }; + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, participantsAccountIDs[0]]); + return moneyRequestOptions.length === 1 && moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT); + }); expect(onlyHaveSplitOption).toBe(true); }); it('has multiple participants excluding self', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, ...participantsAccountIDs]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, ...participantsAccountIDs]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT)).toBe(true); }); it('user has send money permission', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, ...participantsAccountIDs]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, ...participantsAccountIDs]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT)).toBe(true); }); it("it's a group DM report", () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.CHAT, participantsAccountIDs: [currentUserAccountID, ...participantsAccountIDs], }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, ...participantsAccountIDs]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, ...participantsAccountIDs.map(Number)]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT)).toBe(true); }); @@ -488,11 +512,11 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: true, }).then(() => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), parentReportID: '102', type: CONST.REPORT.TYPE.EXPENSE, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); }); @@ -505,7 +529,7 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: true, }).then(() => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.EXPENSE, stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS_NUM.OPEN, @@ -513,32 +537,38 @@ describe('ReportUtils', () => { }; const paidPolicy = { type: CONST.POLICY.TYPE.TEAM, - }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, paidPolicy, [currentUserAccountID, participantsAccountIDs[0]], true); + id: '', + name: '', + role: 'user', + owner: '', + outputCurrency: '', + isPolicyExpenseChatEnabled: false, + } as const; + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, paidPolicy, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(1); }); }); it('it is an IOU report in submitted state', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.IOU, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); }); it('it is an IOU report in submitted state even with send money permissions', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.IOU, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); }); @@ -547,11 +577,11 @@ describe('ReportUtils', () => { describe('return multiple money request option if', () => { it("it is user's own policy expense chat", () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, ...participantsAccountIDs]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, ...participantsAccountIDs]); expect(moneyRequestOptions.length).toBe(2); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT)).toBe(true); @@ -559,10 +589,10 @@ describe('ReportUtils', () => { it('it is a 1:1 DM', () => { const report = { - ...LHNTestUtils.getFakeReport(), + ...(LHNTestUtils.getFakeReport() as Report), type: CONST.REPORT.TYPE.CHAT, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(2); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SEND)).toBe(true); @@ -589,20 +619,20 @@ describe('ReportUtils', () => { describe('sortReportsByLastRead', () => { it('should filter out report without reportID & lastReadTime and sort lastReadTime in ascending order', () => { const reports = [ - {reportID: 1, lastReadTime: '2023-07-08 07:15:44.030'}, - {reportID: 2, lastReadTime: null}, - {reportID: 3, lastReadTime: '2023-07-06 07:15:44.030'}, - {reportID: 4, lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, - {lastReadTime: '2023-07-09 07:15:44.030'}, - {reportID: 6}, - {}, + {reportID: '1', lastReadTime: '2023-07-08 07:15:44.030'}, + {reportID: '2', lastReadTime: undefined}, + {reportID: '3', lastReadTime: '2023-07-06 07:15:44.030'}, + {reportID: '4', lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, + {lastReadTime: '2023-07-09 07:15:44.030'} as Report, + {reportID: '6'}, + null, ]; const sortedReports = [ - {reportID: 3, lastReadTime: '2023-07-06 07:15:44.030'}, - {reportID: 4, lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, - {reportID: 1, lastReadTime: '2023-07-08 07:15:44.030'}, - ]; - expect(ReportUtils.sortReportsByLastRead(reports)).toEqual(sortedReports); + {reportID: '3', lastReadTime: '2023-07-06 07:15:44.030'}, + {reportID: '4', lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, + {reportID: '1', lastReadTime: '2023-07-08 07:15:44.030'}, + ] as const; + expect(ReportUtils.sortReportsByLastRead(reports, null)).toEqual(sortedReports); }); }); @@ -613,26 +643,26 @@ describe('ReportUtils', () => { {reportID: '3', lastReadTime: '2024-02-01 04:56:47.233', parentReportActionID: '2', parentReportID: '2', reportName: 'Report'}, {reportID: '4', lastReadTime: '2024-02-01 04:56:47.233', parentReportActionID: '3', parentReportID: '3', reportName: 'Report'}, {reportID: '5', lastReadTime: '2024-02-01 04:56:47.233', parentReportActionID: '4', parentReportID: '4', reportName: 'Report'}, - ]; + ] as const; const reportActions = [ - {reportActionID: '1', created: '2024-02-01 04:42:22.965'}, - {reportActionID: '2', created: '2024-02-01 04:42:28.003'}, - {reportActionID: '3', created: '2024-02-01 04:42:31.742'}, - {reportActionID: '4', created: '2024-02-01 04:42:35.619'}, - ]; + {reportActionID: '1', created: '2024-02-01 04:42:22.965', actionName: 'MARKEDREIMBURSED'}, + {reportActionID: '2', created: '2024-02-01 04:42:28.003', actionName: 'MARKEDREIMBURSED'}, + {reportActionID: '3', created: '2024-02-01 04:42:31.742', actionName: 'MARKEDREIMBURSED'}, + {reportActionID: '4', created: '2024-02-01 04:42:35.619', actionName: 'MARKEDREIMBURSED'}, + ] as const; beforeAll(() => { Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT}${reports[0].reportID}`]: reports[0], - [`${ONYXKEYS.COLLECTION.REPORT}${reports[1].reportID}`]: reports[1], - [`${ONYXKEYS.COLLECTION.REPORT}${reports[2].reportID}`]: reports[2], - [`${ONYXKEYS.COLLECTION.REPORT}${reports[3].reportID}`]: reports[3], - [`${ONYXKEYS.COLLECTION.REPORT}${reports[4].reportID}`]: reports[4], - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[0].reportID}`]: {[reportActions[0].reportActionID]: reportActions[0]}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[1].reportID}`]: {[reportActions[1].reportActionID]: reportActions[1]}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[2].reportID}`]: {[reportActions[2].reportActionID]: reportActions[2]}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[3].reportID}`]: {[reportActions[3].reportActionID]: reportActions[3]}, + [`${ONYXKEYS.COLLECTION.REPORT}${reports[0].reportID}` as const]: reports[0], + [`${ONYXKEYS.COLLECTION.REPORT}${reports[1].reportID}` as const]: reports[1], + [`${ONYXKEYS.COLLECTION.REPORT}${reports[2].reportID}` as const]: reports[2], + [`${ONYXKEYS.COLLECTION.REPORT}${reports[3].reportID}` as const]: reports[3], + [`${ONYXKEYS.COLLECTION.REPORT}${reports[4].reportID}` as const]: reports[4], + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[0].reportID}` as const]: {[reportActions[0].reportActionID]: reportActions[0]}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[1].reportID}` as const]: {[reportActions[1].reportActionID]: reportActions[1]}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[2].reportID}` as const]: {[reportActions[2].reportActionID]: reportActions[2]}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[3].reportID}` as const]: {[reportActions[3].reportActionID]: reportActions[3]}, }); return waitForBatchedUpdates(); }); From c66339c5ee848f074ef98f3e84a2267d9f98e05d Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 29 Feb 2024 17:02:27 +0000 Subject: [PATCH 02/10] refactor(typescript): apply pull request suggestions --- src/types/utils/CollectionDataSet.ts | 19 +++++ tests/perf-test/SidebarLinks.perf-test.tsx | 5 +- tests/unit/OptionsListUtilsTest.ts | 4 +- tests/unit/ReportUtilsTest.ts | 92 +++++++++++----------- 4 files changed, 71 insertions(+), 49 deletions(-) diff --git a/src/types/utils/CollectionDataSet.ts b/src/types/utils/CollectionDataSet.ts index fc95a69dc9bf..05a0843b6e9b 100644 --- a/src/types/utils/CollectionDataSet.ts +++ b/src/types/utils/CollectionDataSet.ts @@ -1,6 +1,25 @@ +import type {OnyxEntry} from 'react-native-onyx'; import type {OnyxCollectionKey, OnyxCollectionValuesMapping} from '@src/ONYXKEYS'; /** Helps with typing a collection item update inside Onyx.multiSet call */ type CollectionDataSet = Record<`${TCollectionKey}${string}`, OnyxCollectionValuesMapping[TCollectionKey]>; +const toCollectionDataSet = ( + collectionKey: TCollectionKey, + collection: Array>, + idSelector: (collectionValue: OnyxCollectionValuesMapping[TCollectionKey]) => string, +) => { + const collectionDataSet = collection.reduce>((result, collectionValue) => { + if (collectionValue) { + // eslint-disable-next-line no-param-reassign + result[`${collectionKey}${idSelector(collectionValue)}`] = collectionValue; + } + return result; + }, {} as CollectionDataSet); + + return collectionDataSet; +}; + export default CollectionDataSet; + +export {toCollectionDataSet}; diff --git a/tests/perf-test/SidebarLinks.perf-test.tsx b/tests/perf-test/SidebarLinks.perf-test.tsx index c19a34517f45..473e854322d5 100644 --- a/tests/perf-test/SidebarLinks.perf-test.tsx +++ b/tests/perf-test/SidebarLinks.perf-test.tsx @@ -16,9 +16,8 @@ jest.mock('@components/Icon/Expensicons'); jest.mock('@react-navigation/native'); const getMockedReportsMap = (length = 100) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const mockReports = Array.from({length}, (_, i) => { - const reportID = i + 1; + const mockReports = Array.from({length}, (value, index) => { + const reportID = index + 1; const participants = [1, 2]; const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; const report = LHNTestUtils.getFakeReport(participants, 1, true); diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index dfc953577bfa..02402e7d7bd2 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -623,7 +623,9 @@ describe('OptionsListUtils', () => { if (!ReportUtils.canUserPerformWriteAction(report) || ReportUtils.isExpensifyOnlyParticipantInReport(report)) { return reports; } - return {...reports, [reportKey]: report}; + // eslint-disable-next-line no-param-reassign + reports[reportKey] = report; + return reports; }, {}); // When we pass an empty search value diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index b8375700304c..ce60ffeaad68 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -1,9 +1,11 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report} from '@src/types/onyx'; +import type {Policy, Report, ReportAction} from '@src/types/onyx'; +import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -45,7 +47,7 @@ const participantsPersonalDetails = { }, } as const; -const policy = { +const policy: Policy = { id: '1', name: 'Vikings Policy', role: 'user', @@ -53,17 +55,18 @@ const policy = { owner: '', outputCurrency: '', isPolicyExpenseChatEnabled: false, -} as const; +}; Onyx.init({keys: ONYXKEYS}); describe('ReportUtils', () => { beforeAll(() => { + const policyCollectionDataSet = toCollectionDataSet(ONYXKEYS.COLLECTION.POLICY, [policy], (current) => current.id); Onyx.multiSet({ [ONYXKEYS.PERSONAL_DETAILS_LIST]: participantsPersonalDetails, [ONYXKEYS.SESSION]: {email: currentUserEmail, accountID: currentUserAccountID}, [ONYXKEYS.COUNTRY_CODE]: 1, - [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}` as const]: policy, + ...policyCollectionDataSet, }); return waitForBatchedUpdates(); }); @@ -268,14 +271,14 @@ describe('ReportUtils', () => { }); it('returns false when the matched IOU report does not have an owner accountID', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), ownerAccountID: undefined, }; expect(ReportUtils.requiresAttentionFromCurrentUser(report)).toBe(false); }); it('returns false when the linked iou report has an oustanding IOU', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), iouReportID: '1', }; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, { @@ -287,7 +290,7 @@ describe('ReportUtils', () => { }); it('returns false when the report has no outstanding IOU but is waiting for a bank account and the logged user is the report owner', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), ownerAccountID: currentUserAccountID, isWaitingOnBankAccount: true, }; @@ -295,7 +298,7 @@ describe('ReportUtils', () => { }); it('returns false when the report has outstanding IOU and is not waiting for a bank account and the logged user is the report owner', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), ownerAccountID: currentUserAccountID, isWaitingOnBankAccount: false, }; @@ -303,7 +306,7 @@ describe('ReportUtils', () => { }); it('returns false when the report has no oustanding IOU but is waiting for a bank account and the logged user is not the report owner', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), ownerAccountID: 97, isWaitingOnBankAccount: true, }; @@ -311,14 +314,14 @@ describe('ReportUtils', () => { }); it('returns true when the report has an unread mention', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), isUnreadWithMention: true, }; expect(ReportUtils.requiresAttentionFromCurrentUser(report)).toBe(true); }); it('returns true when the report is an outstanding task', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.TASK, managerID: currentUserAccountID, isUnreadWithMention: false, @@ -329,7 +332,7 @@ describe('ReportUtils', () => { }); it('returns true when the report has oustanding child request', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), ownerAccountID: 99, hasOutstandingChildRequest: true, isWaitingOnBankAccount: false, @@ -363,7 +366,7 @@ describe('ReportUtils', () => { it('it is a room with no participants except self', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID]); @@ -372,7 +375,7 @@ describe('ReportUtils', () => { it('its not your policy expense chat', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: false, }; @@ -382,7 +385,7 @@ describe('ReportUtils', () => { it('its paid IOU report', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.IOU, statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, }; @@ -392,7 +395,7 @@ describe('ReportUtils', () => { it('its approved Expense report', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.EXPENSE, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, @@ -403,7 +406,7 @@ describe('ReportUtils', () => { it('its paid Expense report', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.EXPENSE, statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, }; @@ -417,7 +420,7 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: false, }).then(() => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), parentReportID: '100', type: CONST.REPORT.TYPE.EXPENSE, }; @@ -433,7 +436,7 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: true, }).then(() => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.EXPENSE, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, @@ -463,7 +466,7 @@ describe('ReportUtils', () => { CONST.REPORT.CHAT_TYPE.POLICY_ROOM, ].every((chatType) => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), chatType, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, participantsAccountIDs[0]]); @@ -474,7 +477,7 @@ describe('ReportUtils', () => { it('has multiple participants excluding self', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, ...participantsAccountIDs]); @@ -484,7 +487,7 @@ describe('ReportUtils', () => { it('user has send money permission', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, ...participantsAccountIDs]); @@ -494,7 +497,7 @@ describe('ReportUtils', () => { it("it's a group DM report", () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.CHAT, participantsAccountIDs: [currentUserAccountID, ...participantsAccountIDs], }; @@ -512,7 +515,7 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: true, }).then(() => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), parentReportID: '102', type: CONST.REPORT.TYPE.EXPENSE, }; @@ -529,7 +532,7 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: true, }).then(() => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.EXPENSE, stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS_NUM.OPEN, @@ -551,7 +554,7 @@ describe('ReportUtils', () => { it('it is an IOU report in submitted state', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.IOU, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, @@ -563,7 +566,7 @@ describe('ReportUtils', () => { it('it is an IOU report in submitted state even with send money permissions', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.IOU, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, @@ -577,7 +580,7 @@ describe('ReportUtils', () => { describe('return multiple money request option if', () => { it("it is user's own policy expense chat", () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true, }; @@ -589,7 +592,7 @@ describe('ReportUtils', () => { it('it is a 1:1 DM', () => { const report = { - ...(LHNTestUtils.getFakeReport() as Report), + ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.CHAT, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, null, [currentUserAccountID, participantsAccountIDs[0]]); @@ -618,7 +621,7 @@ describe('ReportUtils', () => { describe('sortReportsByLastRead', () => { it('should filter out report without reportID & lastReadTime and sort lastReadTime in ascending order', () => { - const reports = [ + const reports: Array> = [ {reportID: '1', lastReadTime: '2023-07-08 07:15:44.030'}, {reportID: '2', lastReadTime: undefined}, {reportID: '3', lastReadTime: '2023-07-06 07:15:44.030'}, @@ -627,42 +630,41 @@ describe('ReportUtils', () => { {reportID: '6'}, null, ]; - const sortedReports = [ + const sortedReports: Array> = [ {reportID: '3', lastReadTime: '2023-07-06 07:15:44.030'}, {reportID: '4', lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, {reportID: '1', lastReadTime: '2023-07-08 07:15:44.030'}, - ] as const; + ]; expect(ReportUtils.sortReportsByLastRead(reports, null)).toEqual(sortedReports); }); }); describe('getAllAncestorReportActions', () => { - const reports = [ + const reports: Report[] = [ {reportID: '1', lastReadTime: '2024-02-01 04:56:47.233', reportName: 'Report'}, {reportID: '2', lastReadTime: '2024-02-01 04:56:47.233', parentReportActionID: '1', parentReportID: '1', reportName: 'Report'}, {reportID: '3', lastReadTime: '2024-02-01 04:56:47.233', parentReportActionID: '2', parentReportID: '2', reportName: 'Report'}, {reportID: '4', lastReadTime: '2024-02-01 04:56:47.233', parentReportActionID: '3', parentReportID: '3', reportName: 'Report'}, {reportID: '5', lastReadTime: '2024-02-01 04:56:47.233', parentReportActionID: '4', parentReportID: '4', reportName: 'Report'}, - ] as const; + ]; - const reportActions = [ + const reportActions: ReportAction[] = [ {reportActionID: '1', created: '2024-02-01 04:42:22.965', actionName: 'MARKEDREIMBURSED'}, {reportActionID: '2', created: '2024-02-01 04:42:28.003', actionName: 'MARKEDREIMBURSED'}, {reportActionID: '3', created: '2024-02-01 04:42:31.742', actionName: 'MARKEDREIMBURSED'}, {reportActionID: '4', created: '2024-02-01 04:42:35.619', actionName: 'MARKEDREIMBURSED'}, - ] as const; + ]; beforeAll(() => { + const reportCollectionDataSet = toCollectionDataSet(ONYXKEYS.COLLECTION.REPORT, reports, (report) => report.reportID); + const reportActionCollectionDataSet = toCollectionDataSet( + ONYXKEYS.COLLECTION.REPORT_ACTIONS, + reportActions.map((reportAction) => ({[reportAction.reportActionID]: reportAction})), + (actions) => Object.values(actions)[0].reportActionID, + ); Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT}${reports[0].reportID}` as const]: reports[0], - [`${ONYXKEYS.COLLECTION.REPORT}${reports[1].reportID}` as const]: reports[1], - [`${ONYXKEYS.COLLECTION.REPORT}${reports[2].reportID}` as const]: reports[2], - [`${ONYXKEYS.COLLECTION.REPORT}${reports[3].reportID}` as const]: reports[3], - [`${ONYXKEYS.COLLECTION.REPORT}${reports[4].reportID}` as const]: reports[4], - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[0].reportID}` as const]: {[reportActions[0].reportActionID]: reportActions[0]}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[1].reportID}` as const]: {[reportActions[1].reportActionID]: reportActions[1]}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[2].reportID}` as const]: {[reportActions[2].reportActionID]: reportActions[2]}, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reports[3].reportID}` as const]: {[reportActions[3].reportActionID]: reportActions[3]}, + ...reportCollectionDataSet, + ...reportActionCollectionDataSet, }); return waitForBatchedUpdates(); }); From 2a76abbd50660501041cc54a1c32a594594b8036 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 29 Feb 2024 17:08:34 +0000 Subject: [PATCH 03/10] refactor(typescript): resolve type error --- src/libs/DateUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 6da5c8af1ff2..0c8a32cbad23 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -265,7 +265,7 @@ function formatToLongDateWithWeekday(datetime: string | Date): string { * * @returns Sunday */ -function formatToDayOfWeek(datetime: Date): string { +function formatToDayOfWeek(datetime: string | Date): string { return format(new Date(datetime), CONST.DATE.WEEKDAY_TIME_FORMAT); } From a5131ed735744c847d4dd7f5351383a88b61f3b8 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 12 Mar 2024 03:20:52 +0000 Subject: [PATCH 04/10] refactor(typescript): resolve type issues --- tests/e2e/compare/output/console.ts | 4 +- tests/e2e/compare/output/markdown.ts | 27 ++++++------- tests/e2e/compare/types.ts | 57 ---------------------------- tests/e2e/measure/math.ts | 11 +++++- 4 files changed, 26 insertions(+), 73 deletions(-) delete mode 100644 tests/e2e/compare/types.ts diff --git a/tests/e2e/compare/output/console.ts b/tests/e2e/compare/output/console.ts index de8e5d913893..3da0100b603f 100644 --- a/tests/e2e/compare/output/console.ts +++ b/tests/e2e/compare/output/console.ts @@ -13,6 +13,8 @@ type Entry = { type Data = { significance: Entry[]; meaningless: Entry[]; + errors: string[]; + warnings: string[]; }; const printRegularLine = (entry: Entry) => { @@ -36,4 +38,4 @@ export default (data: Data) => { console.debug(''); }; -export type {Entry}; +export type {Data, Entry}; diff --git a/tests/e2e/compare/output/markdown.ts b/tests/e2e/compare/output/markdown.ts index 07cf9897e570..716dbd6d09a4 100644 --- a/tests/e2e/compare/output/markdown.ts +++ b/tests/e2e/compare/output/markdown.ts @@ -1,16 +1,17 @@ // From: https://raw.githubusercontent.com/callstack/reassure/main/packages/reassure-compare/src/output/markdown.ts import fs from 'node:fs/promises'; import path from 'path'; +import type {Stats} from 'tests/e2e/measure/math'; import * as Logger from '../../utils/logger'; -import type {AddedEntry, CompareEntry, CompareResult, PerformanceEntry, RemovedEntry} from '../types'; +import type {Data, Entry} from './console'; import * as format from './format'; import markdownTable from './markdownTable'; const tableHeader = ['Name', 'Duration']; -const collapsibleSection = (title: string, content: string): string => `
\n${title}\n\n${content}\n
\n\n`; +const collapsibleSection = (title: string, content: string) => `
\n${title}\n\n${content}\n
\n\n`; -const buildDurationDetails = (title: string, entry: PerformanceEntry): string => { +const buildDurationDetails = (title: string, entry: Stats) => { const relativeStdev = entry.stdev / entry.mean; return [ @@ -23,25 +24,25 @@ const buildDurationDetails = (title: string, entry: PerformanceEntry): string => .join('
'); }; -const buildDurationDetailsEntry = (entry: CompareEntry | AddedEntry | RemovedEntry): string => +const buildDurationDetailsEntry = (entry: Entry) => ['baseline' in entry ? buildDurationDetails('Baseline', entry.baseline) : '', 'current' in entry ? buildDurationDetails('Current', entry.current) : ''] .filter(Boolean) .join('

'); -const formatEntryDuration = (entry: CompareEntry | AddedEntry | RemovedEntry): string => { - if ('baseline' in entry && 'current' in entry) { +const formatEntryDuration = (entry: Entry) => { + if (entry.baseline && entry.current) { return format.formatDurationDiffChange(entry); } - if ('baseline' in entry) { + if (entry.baseline) { return format.formatDuration(entry.baseline.mean); } - if ('current' in entry) { + if (entry.current) { return format.formatDuration(entry.current.mean); } return ''; }; -const buildDetailsTable = (entries: Array): string => { +const buildDetailsTable = (entries: Entry[]) => { if (!entries.length) { return ''; } @@ -52,7 +53,7 @@ const buildDetailsTable = (entries: Array, collapse = false): string => { +const buildSummaryTable = (entries: Entry[], collapse = false) => { if (!entries.length) { return '_There are no entries_'; } @@ -63,7 +64,7 @@ const buildSummaryTable = (entries: Array { +const buildMarkdown = (data: Data) => { let result = '## Performance Comparison Report 📊'; if (data.errors?.length) { @@ -91,7 +92,7 @@ const buildMarkdown = (data: CompareResult): string => { return result; }; -const writeToFile = (filePath: string, content: string): Promise => +const writeToFile = (filePath: string, content: string) => fs .writeFile(filePath, content) .then(() => { @@ -105,7 +106,7 @@ const writeToFile = (filePath: string, content: string): Promise => throw error; }); -const writeToMarkdown = (filePath: string, data: CompareResult): Promise => { +const writeToMarkdown = (filePath: string, data: Data) => { const markdown = buildMarkdown(data); return writeToFile(filePath, markdown).catch((error) => { console.error(error); diff --git a/tests/e2e/compare/types.ts b/tests/e2e/compare/types.ts deleted file mode 100644 index e0be36716977..000000000000 --- a/tests/e2e/compare/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -type Entries = number[]; - -/** Metadata information for performance results. */ - -/** Entry in the performance results file. */ -type PerformanceEntry = { - /** Number of times the measurement test was run. */ - runs: number; - - /** Arithmetic average of measured render/execution durations for each run. */ - mean: number; - - /** Standard deviation of measured render/execution durations for each run. */ - stdev: number; - - /** Array of measured render/execution durations for each run. */ - entries: Entries; -}; - -/** - * Compare entry for tests that have both baseline and current entry - */ -type CompareEntry = { - name: string; - current: PerformanceEntry; - baseline: PerformanceEntry; - durationDiff: number; - relativeDurationDiff: number; -}; - -/** - * Compare entry for tests that have only current entry - */ -type AddedEntry = { - name: string; - current: PerformanceEntry; -}; - -/** - * Compare entry for tests that have only baseline entry - */ -type RemovedEntry = { - name: string; - baseline: PerformanceEntry; -}; - -/** Output of compare function. */ -type CompareResult = { - significance: CompareEntry[]; - meaningless: CompareEntry[]; - added: AddedEntry[]; - removed: RemovedEntry[]; - errors?: string[]; - warnings?: string[]; -}; - -export type {Entries, PerformanceEntry, CompareEntry, AddedEntry, RemovedEntry, CompareResult}; diff --git a/tests/e2e/measure/math.ts b/tests/e2e/measure/math.ts index e632e9bec232..d444ab0e79da 100644 --- a/tests/e2e/measure/math.ts +++ b/tests/e2e/measure/math.ts @@ -1,4 +1,11 @@ -import type {Entries, PerformanceEntry} from '../compare/types'; +type Entries = number[]; + +type Stats = { + mean: number; + stdev: number; + runs: number; + entries: Entries; +}; const filterOutliersViaIQR = (data: Entries): Entries => { let q1; @@ -28,7 +35,7 @@ const std = (arr: Entries): number => { return Math.sqrt(arr.map((i) => (i - avg) ** 2).reduce((a, b) => a + b) / arr.length); }; -const getStats = (entries: Entries): PerformanceEntry => { +const getStats = (entries: Entries): Stats => { const cleanedEntries = filterOutliersViaIQR(entries); const meanDuration = mean(cleanedEntries); const stdevDuration = std(cleanedEntries); From e5d3ef0982f1225294adf4d02db9951f3608c48f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 13 Mar 2024 15:51:21 +0000 Subject: [PATCH 05/10] fix: wrong data type conversion --- tests/perf-test/SidebarLinks.perf-test.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/perf-test/SidebarLinks.perf-test.tsx b/tests/perf-test/SidebarLinks.perf-test.tsx index 473e854322d5..2848015d5c63 100644 --- a/tests/perf-test/SidebarLinks.perf-test.tsx +++ b/tests/perf-test/SidebarLinks.perf-test.tsx @@ -16,16 +16,18 @@ jest.mock('@components/Icon/Expensicons'); jest.mock('@react-navigation/native'); const getMockedReportsMap = (length = 100) => { - const mockReports = Array.from({length}, (value, index) => { - const reportID = index + 1; - const participants = [1, 2]; - const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; - const report = LHNTestUtils.getFakeReport(participants, 1, true); - - return {[reportKey]: report}; - }); - - return {...mockReports}; + const mockReports = Object.fromEntries( + Array.from({length}, (value, index) => { + const reportID = index + 1; + const participants = [1, 2]; + const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; + const report = LHNTestUtils.getFakeReport(participants, 1, true); + + return [reportKey, report]; + }), + ); + + return mockReports; }; const mockedResponseMap = getMockedReportsMap(500); From c5b2ada306f9397b1b7eff5a9cf5815348fca572 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 14 Mar 2024 19:00:17 +0000 Subject: [PATCH 06/10] refactor(typescript): apply pull request suggestions --- src/libs/OptionsListUtils.ts | 2 +- src/libs/ReportUtils.ts | 4 ++ tests/unit/OptionsListUtilsTest.ts | 108 ++++++++++++++++++----------- tests/unit/ReportUtilsTest.ts | 6 +- 4 files changed, 75 insertions(+), 45 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index ecf9b68961ca..6c30f9c3568e 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -2076,4 +2076,4 @@ export { getShareLogOptions, }; -export type {MemberForList, CategorySection, GetOptions, PayeePersonalDetails, Tag}; +export type {MemberForList, CategorySection, CategoryTreeSection, GetOptions, PayeePersonalDetails, Tag}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 76e0267b6f35..53cb6d849ec7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -29,6 +29,7 @@ import type { ReportMetadata, Session, Task, + TaxRate, Transaction, TransactionViolation, UserWallet, @@ -404,6 +405,9 @@ type OptionData = { isDisabled?: boolean | null; name?: string | null; isSelfDM?: boolean | null; + reportID?: string; + enabled?: boolean; + data?: Partial; } & Report; type OnyxDataTaskAssigneeChat = { diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 02402e7d7bd2..bfb9514d7b92 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -1,16 +1,19 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import type {Tag} from '@src/libs/OptionsListUtils'; import * as OptionsListUtils from '@src/libs/OptionsListUtils'; import * as ReportUtils from '@src/libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyCategories, Report, TaxRatesWithDefault} from '@src/types/onyx'; +import type {PersonalDetails, Policy, PolicyCategories, Report, TaxRatesWithDefault} from '@src/types/onyx'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +type PersonalDetailsList = Record; + describe('OptionsListUtils', () => { // Given a set of reports with both single participants and multiple participants some pinned and some not - const REPORTS: Record = { + const REPORTS: OnyxCollection = { '1': { lastReadTime: '2021-01-14 11:25:39.295', lastVisibleActionCreated: '2022-11-22 03:26:02.015', @@ -133,7 +136,7 @@ describe('OptionsListUtils', () => { }; // And a set of personalDetails some with existing reports and some without - const PERSONAL_DETAILS = { + const PERSONAL_DETAILS: PersonalDetailsList = { // These exist in our reports '1': { accountID: 1, @@ -200,7 +203,7 @@ describe('OptionsListUtils', () => { }, }; - const REPORTS_WITH_CONCIERGE = { + const REPORTS_WITH_CONCIERGE: OnyxCollection = { ...REPORTS, '11': { @@ -215,7 +218,7 @@ describe('OptionsListUtils', () => { }, }; - const REPORTS_WITH_CHRONOS = { + const REPORTS_WITH_CHRONOS: OnyxCollection = { ...REPORTS, '12': { lastReadTime: '2021-01-14 11:25:39.302', @@ -229,7 +232,7 @@ describe('OptionsListUtils', () => { }, }; - const REPORTS_WITH_RECEIPTS = { + const REPORTS_WITH_RECEIPTS: OnyxCollection = { ...REPORTS, '13': { lastReadTime: '2021-01-14 11:25:39.302', @@ -243,7 +246,7 @@ describe('OptionsListUtils', () => { }, }; - const REPORTS_WITH_WORKSPACE_ROOMS = { + const REPORTS_WITH_WORKSPACE_ROOMS: OnyxCollection = { ...REPORTS, '14': { lastReadTime: '2021-01-14 11:25:39.302', @@ -254,62 +257,67 @@ describe('OptionsListUtils', () => { visibleChatMemberAccountIDs: [1, 10, 3], reportName: '', oldPolicyName: 'Avengers Room', - isArchivedRoom: false, chatType: CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, isOwnPolicyExpenseChat: true, type: CONST.REPORT.TYPE.CHAT, }, }; - const PERSONAL_DETAILS_WITH_CONCIERGE = { + const PERSONAL_DETAILS_WITH_CONCIERGE: PersonalDetailsList = { ...PERSONAL_DETAILS, '999': { accountID: 999, displayName: 'Concierge', login: 'concierge@expensify.com', + reportID: '', }, }; - const PERSONAL_DETAILS_WITH_CHRONOS = { + const PERSONAL_DETAILS_WITH_CHRONOS: PersonalDetailsList = { ...PERSONAL_DETAILS, '1000': { accountID: 1000, displayName: 'Chronos', login: 'chronos@expensify.com', + reportID: '', }, }; - const PERSONAL_DETAILS_WITH_RECEIPTS = { + const PERSONAL_DETAILS_WITH_RECEIPTS: PersonalDetailsList = { ...PERSONAL_DETAILS, '1001': { accountID: 1001, displayName: 'Receipts', login: 'receipts@expensify.com', + reportID: '', }, }; - const PERSONAL_DETAILS_WITH_PERIODS = { + const PERSONAL_DETAILS_WITH_PERIODS: PersonalDetailsList = { ...PERSONAL_DETAILS, '1002': { accountID: 1002, displayName: 'The Flash', login: 'barry.allen@expensify.com', + reportID: '', }, }; - const POLICY = { - id: 'ABC123', + const policyID = 'ABC123'; + + const POLICY: Policy = { + id: policyID, name: 'Hero Policy', role: 'user', type: 'free', owner: '', outputCurrency: '', isPolicyExpenseChatEnabled: false, - } as const; + }; // Set the currently logged in user, report data, and personal details beforeAll(() => { @@ -322,7 +330,7 @@ describe('OptionsListUtils', () => { ownerAccountID: 8, total: 1000, }, - [`${ONYXKEYS.COLLECTION.POLICY}${POLICY.id}` as const]: POLICY, + [`${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const]: POLICY, }, }); Onyx.registerLogger(() => {}); @@ -619,7 +627,7 @@ describe('OptionsListUtils', () => { it('getShareDestinationsOptions()', () => { // Filter current REPORTS as we do in the component, before getting share destination options - const filteredReports = Object.entries(REPORTS).reduce>((reports, [reportKey, report]) => { + const filteredReports = Object.entries(REPORTS).reduce>((reports, [reportKey, report]) => { if (!ReportUtils.canUserPerformWriteAction(report) || ReportUtils.isExpensifyOnlyParticipantInReport(report)) { return reports; } @@ -647,7 +655,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports.length).toBe(1); // Filter current REPORTS_WITH_WORKSPACE_ROOMS as we do in the component, before getting share destination options - const filteredReportsWithWorkspaceRooms = Object.entries(REPORTS_WITH_WORKSPACE_ROOMS).reduce>((reports, [reportKey, report]) => { + const filteredReportsWithWorkspaceRooms = Object.entries(REPORTS_WITH_WORKSPACE_ROOMS).reduce>((reports, [reportKey, report]) => { if (!ReportUtils.canUserPerformWriteAction(report) || ReportUtils.isExpensifyOnlyParticipantInReport(report)) { return reports; } @@ -703,7 +711,7 @@ describe('OptionsListUtils', () => { const emptySearch = ''; const wrongSearch = 'bla bla'; const recentlyUsedCategories = ['Taxi', 'Restaurant']; - const selectedOptions = [ + const selectedOptions: Array> = [ { name: 'Medical', enabled: true, @@ -713,6 +721,7 @@ describe('OptionsListUtils', () => { Taxi: { enabled: false, name: 'Taxi', + unencodedName: 'Taxi', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -721,6 +730,7 @@ describe('OptionsListUtils', () => { Restaurant: { enabled: true, name: 'Restaurant', + unencodedName: 'Restaurant', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -729,6 +739,7 @@ describe('OptionsListUtils', () => { Food: { enabled: true, name: 'Food', + unencodedName: 'Food', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -737,13 +748,14 @@ describe('OptionsListUtils', () => { 'Food: Meat': { enabled: true, name: 'Food: Meat', + unencodedName: 'Food: Meat', areCommentsRequired: false, 'GL Code': '', externalID: '', origin: '', }, }; - const smallResultList = [ + const smallResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: false, @@ -776,7 +788,7 @@ describe('OptionsListUtils', () => { ], }, ]; - const smallSearchResultList = [ + const smallSearchResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: true, @@ -801,7 +813,7 @@ describe('OptionsListUtils', () => { ], }, ]; - const smallWrongSearchResultList = [ + const smallWrongSearchResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: true, @@ -813,6 +825,7 @@ describe('OptionsListUtils', () => { Taxi: { enabled: false, name: 'Taxi', + unencodedName: 'Taxi', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -821,6 +834,7 @@ describe('OptionsListUtils', () => { Restaurant: { enabled: true, name: 'Restaurant', + unencodedName: 'Restaurant', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -829,6 +843,7 @@ describe('OptionsListUtils', () => { Food: { enabled: true, name: 'Food', + unencodedName: 'Food', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -837,6 +852,7 @@ describe('OptionsListUtils', () => { 'Food: Meat': { enabled: true, name: 'Food: Meat', + unencodedName: 'Food: Meat', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -845,6 +861,7 @@ describe('OptionsListUtils', () => { 'Food: Milk': { enabled: true, name: 'Food: Milk', + unencodedName: 'Food: Milk', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -853,6 +870,7 @@ describe('OptionsListUtils', () => { 'Food: Vegetables': { enabled: false, name: 'Food: Vegetables', + unencodedName: 'Food: Vegetables', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -861,6 +879,7 @@ describe('OptionsListUtils', () => { 'Cars: Audi': { enabled: true, name: 'Cars: Audi', + unencodedName: 'Cars: Audi', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -869,6 +888,7 @@ describe('OptionsListUtils', () => { 'Cars: BMW': { enabled: false, name: 'Cars: BMW', + unencodedName: 'Cars: BMW', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -877,6 +897,7 @@ describe('OptionsListUtils', () => { 'Cars: Mercedes-Benz': { enabled: true, name: 'Cars: Mercedes-Benz', + unencodedName: 'Cars: Mercedes-Benz', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -885,6 +906,7 @@ describe('OptionsListUtils', () => { Medical: { enabled: false, name: 'Medical', + unencodedName: 'Medical', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -893,6 +915,7 @@ describe('OptionsListUtils', () => { 'Travel: Meals': { enabled: true, name: 'Travel: Meals', + unencodedName: 'Travel: Meals', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -901,6 +924,7 @@ describe('OptionsListUtils', () => { 'Travel: Meals: Breakfast': { enabled: true, name: 'Travel: Meals: Breakfast', + unencodedName: 'Travel: Meals: Breakfast', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -909,6 +933,7 @@ describe('OptionsListUtils', () => { 'Travel: Meals: Dinner': { enabled: false, name: 'Travel: Meals: Dinner', + unencodedName: 'Travel: Meals: Dinner', areCommentsRequired: false, 'GL Code': '', externalID: '', @@ -917,13 +942,14 @@ describe('OptionsListUtils', () => { 'Travel: Meals: Lunch': { enabled: true, name: 'Travel: Meals: Lunch', + unencodedName: 'Travel: Meals: Lunch', areCommentsRequired: false, 'GL Code': '', externalID: '', origin: '', }, }; - const largeResultList = [ + const largeResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: false, @@ -1050,7 +1076,7 @@ describe('OptionsListUtils', () => { ], }, ]; - const largeSearchResultList = [ + const largeSearchResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: true, @@ -1083,7 +1109,7 @@ describe('OptionsListUtils', () => { ], }, ]; - const largeWrongSearchResultList = [ + const largeWrongSearchResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: true, @@ -1092,7 +1118,7 @@ describe('OptionsListUtils', () => { }, ]; const emptyCategoriesList = {}; - const emptySelectedResultList = [ + const emptySelectedResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: false, @@ -1201,7 +1227,7 @@ describe('OptionsListUtils', () => { accountID: null, }, }; - const smallResultList = [ + const smallResultList: OptionsListUtils.CategorySection[] = [ { title: '', shouldShow: false, @@ -1232,7 +1258,7 @@ describe('OptionsListUtils', () => { ], }, ]; - const smallSearchResultList = [ + const smallSearchResultList: OptionsListUtils.CategorySection[] = [ { title: '', shouldShow: true, @@ -1248,7 +1274,7 @@ describe('OptionsListUtils', () => { ], }, ]; - const smallWrongSearchResultList = [ + const smallWrongSearchResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: true, @@ -1313,7 +1339,7 @@ describe('OptionsListUtils', () => { accountID: null, }, }; - const largeResultList = [ + const largeResultList: OptionsListUtils.CategorySection[] = [ { title: '', shouldShow: true, @@ -1400,7 +1426,7 @@ describe('OptionsListUtils', () => { ], }, ]; - const largeSearchResultList = [ + const largeSearchResultList: OptionsListUtils.CategorySection[] = [ { title: '', shouldShow: true, @@ -1423,7 +1449,7 @@ describe('OptionsListUtils', () => { ], }, ]; - const largeWrongSearchResultList = [ + const largeWrongSearchResultList: OptionsListUtils.CategoryTreeSection[] = [ { title: '', shouldShow: true, @@ -2177,25 +2203,25 @@ describe('OptionsListUtils', () => { CODE2: { name: 'Tax rate 2', value: '3%', - code: '', - modifiedName: '', + code: 'CODE2', + modifiedName: 'Tax rate 2 (3%)', }, CODE3: { name: 'Tax option 3', value: '5%', - code: '', - modifiedName: '', + code: 'CODE3', + modifiedName: 'Tax option 3 (5%)', }, CODE1: { name: 'Tax exempt 1', value: '0%', - code: '', - modifiedName: '', + code: 'CODE1', + modifiedName: 'Tax exempt 1 (0%) • Default', }, }, }; - const resultList = [ + const resultList: OptionsListUtils.CategorySection[] = [ { title: '', shouldShow: false, @@ -2248,7 +2274,7 @@ describe('OptionsListUtils', () => { }, ]; - const searchResultList = [ + const searchResultList: OptionsListUtils.CategorySection[] = [ { title: '', shouldShow: true, @@ -2272,7 +2298,7 @@ describe('OptionsListUtils', () => { }, ]; - const wrongSearchResultList = [ + const wrongSearchResultList: OptionsListUtils.CategorySection[] = [ { title: '', shouldShow: true, diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 41ac11d3a925..9bb2b3b5dcc2 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -4,7 +4,7 @@ import Onyx from 'react-native-onyx'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Policy, Report, ReportAction} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, Report, ReportAction} from '@src/types/onyx'; import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -14,7 +14,7 @@ jest.mock('@libs/Permissions'); const currentUserEmail = 'bjorn@vikings.net'; const currentUserAccountID = 5; -const participantsPersonalDetails = { +const participantsPersonalDetails: PersonalDetailsList = { '1': { accountID: 1, displayName: 'Ragnar Lothbrok', @@ -45,7 +45,7 @@ const participantsPersonalDetails = { login: 'lagertha2@vikings.net', pronouns: 'She/her', }, -} as const; +}; const policy: Policy = { id: '1', From f051c2ef1c3032484138c42af04bda18b246a6e3 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 18 Mar 2024 19:01:10 +0000 Subject: [PATCH 07/10] refactor(typescript): apply pull request suggestion --- tests/unit/OptionsListUtilsTest.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index bfb9514d7b92..1d5632f0efd9 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import type {Tag} from '@src/libs/OptionsListUtils'; @@ -627,7 +627,7 @@ describe('OptionsListUtils', () => { it('getShareDestinationsOptions()', () => { // Filter current REPORTS as we do in the component, before getting share destination options - const filteredReports = Object.entries(REPORTS).reduce>((reports, [reportKey, report]) => { + const filteredReports = Object.entries(REPORTS).reduce>>((reports, [reportKey, report]) => { if (!ReportUtils.canUserPerformWriteAction(report) || ReportUtils.isExpensifyOnlyParticipantInReport(report)) { return reports; } @@ -655,7 +655,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports.length).toBe(1); // Filter current REPORTS_WITH_WORKSPACE_ROOMS as we do in the component, before getting share destination options - const filteredReportsWithWorkspaceRooms = Object.entries(REPORTS_WITH_WORKSPACE_ROOMS).reduce>((reports, [reportKey, report]) => { + const filteredReportsWithWorkspaceRooms = Object.entries(REPORTS_WITH_WORKSPACE_ROOMS).reduce>>((reports, [reportKey, report]) => { if (!ReportUtils.canUserPerformWriteAction(report) || ReportUtils.isExpensifyOnlyParticipantInReport(report)) { return reports; } From 3e40a53d735f50c9aa3d80c88a74cccd1c425074 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 20 Mar 2024 17:12:59 +0000 Subject: [PATCH 08/10] refactor(typescript): apply pull request feedback --- src/libs/DateUtils.ts | 2 +- tests/unit/DateUtilsTest.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index dee66c3993e3..4d4f8d425681 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -266,7 +266,7 @@ function formatToLongDateWithWeekday(datetime: string | Date): string { * * @returns Sunday */ -function formatToDayOfWeek(datetime: string | Date): string { +function formatToDayOfWeek(datetime: Date): string { return format(new Date(datetime), CONST.DATE.WEEKDAY_TIME_FORMAT); } diff --git a/tests/unit/DateUtilsTest.ts b/tests/unit/DateUtilsTest.ts index 8758050e6f62..a7f43ea84045 100644 --- a/tests/unit/DateUtilsTest.ts +++ b/tests/unit/DateUtilsTest.ts @@ -42,7 +42,7 @@ describe('DateUtils', () => { }); it('formatToDayOfWeek should return a weekday', () => { - const weekDay = DateUtils.formatToDayOfWeek(datetime); + const weekDay = DateUtils.formatToDayOfWeek(new Date(datetime)); expect(weekDay).toBe('Monday'); }); it('formatToLocalTime should return a date in a local format', () => { From ba09987cd08cf5a96b7dfcc83604c37639efabff Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 22 Mar 2024 15:21:31 +0000 Subject: [PATCH 09/10] refactor(typescript): apply pull request suggestion --- src/libs/DateUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 4d4f8d425681..44c7682b47f2 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -267,7 +267,7 @@ function formatToLongDateWithWeekday(datetime: string | Date): string { * @returns Sunday */ function formatToDayOfWeek(datetime: Date): string { - return format(new Date(datetime), CONST.DATE.WEEKDAY_TIME_FORMAT); + return format(datetime, CONST.DATE.WEEKDAY_TIME_FORMAT); } /** From 9ab0ea3d31d145d46b7e7be19566a9921a1f395c Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 22 Mar 2024 17:13:56 +0000 Subject: [PATCH 10/10] refactor(typescript): apply pull request feedback --- tests/e2e/compare/output/markdown.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/e2e/compare/output/markdown.ts b/tests/e2e/compare/output/markdown.ts index 716dbd6d09a4..34bc3251c422 100644 --- a/tests/e2e/compare/output/markdown.ts +++ b/tests/e2e/compare/output/markdown.ts @@ -29,17 +29,22 @@ const buildDurationDetailsEntry = (entry: Entry) => .filter(Boolean) .join('

'); -const formatEntryDuration = (entry: Entry) => { - if (entry.baseline && entry.current) { - return format.formatDurationDiffChange(entry); +const formatEntryDuration = (entry: Entry): string => { + let formattedDuration = ''; + + if ('baseline' in entry && 'current' in entry) { + formattedDuration = format.formatDurationDiffChange(entry); } - if (entry.baseline) { - return format.formatDuration(entry.baseline.mean); + + if ('baseline' in entry) { + formattedDuration = format.formatDuration(entry.baseline.mean); } - if (entry.current) { - return format.formatDuration(entry.current.mean); + + if ('current' in entry) { + formattedDuration = format.formatDuration(entry.current.mean); } - return ''; + + return formattedDuration; }; const buildDetailsTable = (entries: Entry[]) => {