diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 112379d1e21a9..620d5d4526741 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -21,7 +21,6 @@ import useRootNavigationState from '@hooks/useRootNavigationState'; import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isValidDraftComment} from '@libs/DraftCommentUtils'; import getPlatform from '@libs/getPlatform'; import Log from '@libs/Log'; import {getMovedReportID} from '@libs/ModifiedExpenseMessage'; @@ -191,7 +190,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio ? (getOriginalMessage(itemParentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) : CONST.DEFAULT_NUMBER_ID; const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; - const hasDraftComment = isValidDraftComment(draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]); + const hasDraftComment = !!draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]; const isReportArchived = !!itemReportNameValuePairs?.private_isArchived; const canUserPerformWrite = canUserPerformWriteActionUtil(item, isReportArchived); diff --git a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx index 4e1d610df97dd..1b39219c004d0 100644 --- a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx @@ -57,6 +57,7 @@ function UserSelectPopup({value, closeOverlay, onChange}: UserSelectPopupProps) const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const [searchTerm, setSearchTerm] = useState(''); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const initialSelectedOptions = useMemo(() => { return value.reduce((acc, id) => { const participant = personalDetails?.[id]; @@ -87,12 +88,13 @@ function UserSelectPopup({value, closeOverlay, onChange}: UserSelectPopupProps) reports: options.reports, personalDetails: options.personalDetails, }, + draftComments, { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, includeCurrentUser: true, }, ); - }, [options.reports, options.personalDetails]); + }, [options.reports, options.personalDetails, draftComments]); const filteredOptions = useMemo(() => { return filterAndOrderOptions(optionsList, cleanSearchTerm, countryCode, { diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 65dc4de2f00ff..073e3a196b8af 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -179,6 +179,7 @@ function SearchAutocompleteList({ const {shouldUseNarrowLayout} = useResponsiveLayout(); const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const [recentSearches] = useOnyx(ONYXKEYS.RECENT_SEARCHES, {canBeMissing: true}); const taxRates = getAllTaxRates(); @@ -187,8 +188,20 @@ function SearchAutocompleteList({ if (!areOptionsInitialized) { return defaultListOptions; } - return getSearchOptions(options, betas ?? [], true, true, autocompleteQueryValue, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS, true, true, false, true); - }, [areOptionsInitialized, betas, options, autocompleteQueryValue]); + return getSearchOptions({ + options, + draftComments, + betas: betas ?? [], + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: autocompleteQueryValue, + maxResults: CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS, + includeUserToInvite: true, + includeRecentReports: true, + includeCurrentUser: true, + shouldShowGBR: false, + }); + }, [areOptionsInitialized, options, draftComments, betas, autocompleteQueryValue]); const [isInitialRender, setIsInitialRender] = useState(true); const parsedQuery = parseForAutocomplete(autocompleteQueryValue); @@ -378,9 +391,19 @@ function SearchAutocompleteList({ case CONST.SEARCH.SYNTAX_FILTER_KEYS.PAYER: case CONST.SEARCH.SYNTAX_FILTER_KEYS.ATTENDEE: case CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTER: { - const participants = getSearchOptions(options, betas ?? [], true, true, autocompleteValue, 10, false, false, true, true).personalDetails.filter( - (participant) => participant.text && !alreadyAutocompletedKeys.includes(participant.text.toLowerCase()), - ); + const participants = getSearchOptions({ + options, + draftComments, + betas: betas ?? [], + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: autocompleteValue, + maxResults: 10, + includeUserToInvite: false, + includeRecentReports: false, + includeCurrentUser: true, + shouldShowGBR: true, + }).personalDetails.filter((participant) => participant.text && !alreadyAutocompletedKeys.includes(participant.text.toLowerCase())); return participants.map((participant) => ({ filterKey: autocompleteKey, @@ -390,7 +413,19 @@ function SearchAutocompleteList({ })); } case CONST.SEARCH.SYNTAX_FILTER_KEYS.IN: { - const filteredReports = getSearchOptions(options, betas ?? [], true, true, autocompleteValue, 10, false, true, false, true).recentReports; + const filteredReports = getSearchOptions({ + options, + draftComments, + betas: betas ?? [], + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: autocompleteValue, + maxResults: 10, + includeUserToInvite: false, + includeRecentReports: true, + includeCurrentUser: false, + shouldShowGBR: true, + }).recentReports; return filteredReports.map((chat) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.IN, @@ -547,7 +582,9 @@ function SearchAutocompleteList({ recentCurrencyAutocompleteList, taxAutocompleteList, options, + draftComments, betas, + currentUserLogin, typeAutocompleteList, groupByAutocompleteList, statusAutocompleteList, @@ -557,7 +594,6 @@ function SearchAutocompleteList({ cardAutocompleteList, booleanTypes, workspaceList, - currentUserLogin, isAutocompleteList, hasAutocompleteList, ]); diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index f641c4bb01cc9..e328d300f94c5 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -53,6 +53,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const [selectedReportIDs, setSelectedReportIDs] = useState(initialReportIDs); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const cleanSearchTerm = useMemo(() => searchTerm.trim().toLowerCase(), [searchTerm]); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const selectedOptions = useMemo(() => { return selectedReportIDs.map((id) => { @@ -66,8 +67,8 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen if (!areOptionsInitialized || !isScreenTransitionEnd) { return defaultListOptions; } - return getSearchOptions(options, undefined, false); - }, [areOptionsInitialized, isScreenTransitionEnd, options]); + return getSearchOptions({options, draftComments, betas: undefined, isUsedInChatFinder: false}); + }, [areOptionsInitialized, draftComments, isScreenTransitionEnd, options]); const chatOptions = useMemo(() => { return filterAndOrderOptions(defaultOptions, cleanSearchTerm, countryCode, { diff --git a/src/components/Search/SearchFiltersParticipantsSelector.tsx b/src/components/Search/SearchFiltersParticipantsSelector.tsx index 287d60b5586c7..7bb484d93eb31 100644 --- a/src/components/Search/SearchFiltersParticipantsSelector.tsx +++ b/src/components/Search/SearchFiltersParticipantsSelector.tsx @@ -52,6 +52,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: const [selectedOptions, setSelectedOptions] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const cleanSearchTerm = useMemo(() => searchTerm.trim().toLowerCase(), [searchTerm]); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const defaultOptions = useMemo(() => { if (!areOptionsInitialized) { @@ -63,12 +64,13 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: reports: options.reports, personalDetails: options.personalDetails, }, + draftComments, { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, includeCurrentUser: true, }, ); - }, [areOptionsInitialized, options.personalDetails, options.reports]); + }, [areOptionsInitialized, draftComments, options.personalDetails, options.reports]); const unselectedOptions = useMemo(() => { return filterSelectedOptions(defaultOptions, new Set(selectedOptions.map((option) => option.accountID))); diff --git a/src/hooks/useSearchSelector.base.ts b/src/hooks/useSearchSelector.base.ts index 9a2e33f1a23d0..64294892e548c 100644 --- a/src/hooks/useSearchSelector.base.ts +++ b/src/hooks/useSearchSelector.base.ts @@ -153,6 +153,7 @@ function useSearchSelectorBase({ const [selectedOptions, setSelectedOptions] = useState(initialSelected ?? []); const [maxResults, setMaxResults] = useState(maxResultsPerPage); const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const onListEndReached = useCallback(() => { setMaxResults((previous) => previous + maxResultsPerPage); @@ -169,9 +170,18 @@ function useSearchSelectorBase({ switch (searchContext) { case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_SEARCH: - return getSearchOptions(optionsWithContacts, betas ?? [], true, true, computedSearchTerm, maxResults, includeUserToInvite); + return getSearchOptions({ + options: optionsWithContacts, + draftComments, + betas: betas ?? [], + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: computedSearchTerm, + maxResults, + includeUserToInvite, + }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_MEMBER_INVITE: - return getValidOptions(optionsWithContacts, { + return getValidOptions(optionsWithContacts, draftComments, { betas: betas ?? [], includeP2P: true, includeSelectedOptions: false, @@ -183,7 +193,7 @@ function useSearchSelectorBase({ includeUserToInvite, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL: - return getValidOptions(optionsWithContacts, { + return getValidOptions(optionsWithContacts, draftComments, { ...getValidOptionsConfig, betas: betas ?? [], searchString: computedSearchTerm, @@ -193,7 +203,7 @@ function useSearchSelectorBase({ loginsToExclude: excludeLogins, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_SHARE_DESTINATION: - return getValidOptions(optionsWithContacts, { + return getValidOptions(optionsWithContacts, draftComments, { betas, selectedOptions, includeMultipleParticipantReports: true, @@ -217,6 +227,7 @@ function useSearchSelectorBase({ areOptionsInitialized, searchContext, optionsWithContacts, + draftComments, betas, computedSearchTerm, maxResults, diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index 6ca5c3b1e3627..b139457b0eaac 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -71,11 +71,10 @@ function SidebarOrderedReportsContextProvider({ const [transactions, {sourceValue: transactionsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true}); const [transactionViolations, {sourceValue: transactionViolationsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const [reportNameValuePairs, {sourceValue: reportNameValuePairsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true}); - const [, {sourceValue: reportsDraftsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); + const [reportsDrafts, {sourceValue: reportsDraftsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: reportsSelector, canBeMissing: true}); const [currentReportsToDisplay, setCurrentReportsToDisplay] = useState({}); - const {shouldUseNarrowLayout} = useResponsiveLayout(); const {accountID} = useCurrentUserPersonalDetails(); const currentReportIDValue = useCurrentReportID(); @@ -152,18 +151,18 @@ function SidebarOrderedReportsContextProvider({ const shouldDoIncrementalUpdate = updatedReports.length > 0 && Object.keys(currentReportsToDisplay).length > 0; let reportsToDisplay = {}; if (shouldDoIncrementalUpdate) { - reportsToDisplay = SidebarUtils.updateReportsToDisplayInLHN( - currentReportsToDisplay, - chatReports, - updatedReports, - derivedCurrentReportID, - priorityMode === CONST.PRIORITY_MODE.GSD, + reportsToDisplay = SidebarUtils.updateReportsToDisplayInLHN({ + displayedReports: currentReportsToDisplay, + reports: chatReports, + updatedReportsKeys: updatedReports, + currentReportId: derivedCurrentReportID, + isInFocusMode: priorityMode === CONST.PRIORITY_MODE.GSD, betas, - policies, transactionViolations, reportNameValuePairs, reportAttributes, - ); + draftComments: reportsDrafts, + }); } else { reportsToDisplay = SidebarUtils.getReportsToDisplayInLHN( derivedCurrentReportID, @@ -171,6 +170,7 @@ function SidebarOrderedReportsContextProvider({ betas, policies, priorityMode, + reportsDrafts, transactionViolations, reportNameValuePairs, reportAttributes, @@ -180,19 +180,20 @@ function SidebarOrderedReportsContextProvider({ return reportsToDisplay; // Rule disabled intentionally — triggering a re-render on currentReportsToDisplay would cause an infinite loop // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [getUpdatedReports, chatReports, derivedCurrentReportID, priorityMode, betas, policies, transactionViolations, reportNameValuePairs, reportAttributes]); + }, [getUpdatedReports, chatReports, derivedCurrentReportID, priorityMode, betas, policies, transactionViolations, reportNameValuePairs, reportAttributes, reportsDrafts]); const deepComparedReportsToDisplayInLHN = useDeepCompareRef(reportsToDisplayInLHN); + const deepComparedReportsDrafts = useDeepCompareRef(reportsDrafts); useEffect(() => { setCurrentReportsToDisplay(reportsToDisplayInLHN); }, [reportsToDisplayInLHN]); const getOrderedReportIDs = useCallback( - () => SidebarUtils.sortReportsToDisplayInLHN(deepComparedReportsToDisplayInLHN ?? {}, priorityMode, localeCompare, reportNameValuePairs, reportAttributes), + () => SidebarUtils.sortReportsToDisplayInLHN(deepComparedReportsToDisplayInLHN ?? {}, priorityMode, localeCompare, deepComparedReportsDrafts, reportNameValuePairs, reportAttributes), // Rule disabled intentionally - reports should be sorted only when the reportsToDisplayInLHN changes // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - [reportsToDisplayInLHN, localeCompare], + [deepComparedReportsToDisplayInLHN, localeCompare, deepComparedReportsDrafts], ); const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 2ef118b79b4c9..12e944cfb6b59 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -1338,6 +1338,7 @@ function getReasonForShowingRowInLHN({ isReportArchived = false, isInFocusMode = false, betas = undefined, + draftComment, }: { report: OnyxEntry; chatReport: OnyxEntry; @@ -1346,6 +1347,7 @@ function getReasonForShowingRowInLHN({ isReportArchived?: boolean; isInFocusMode?: boolean; betas?: OnyxEntry; + draftComment: string | undefined; }): TranslationPaths | null { if (!report) { return null; @@ -1362,6 +1364,7 @@ function getReasonForShowingRowInLHN({ doesReportHaveViolations, includeSelfDM: true, isReportArchived, + draftComment, }); if (!([CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS, CONST.REPORT_IN_LHN_REASONS.HAS_IOU_VIOLATIONS] as Array).includes(reason) && hasRBR) { diff --git a/src/libs/DraftCommentUtils.ts b/src/libs/DraftCommentUtils.ts deleted file mode 100644 index b3cb32498725e..0000000000000 --- a/src/libs/DraftCommentUtils.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; - -let draftCommentCollection: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - callback: (nextVal) => { - draftCommentCollection = nextVal; - }, - waitForCollectionCallback: true, -}); - -/** - * Returns a draft comment from the onyx collection for given reportID. - * Note: You should use the HOCs/hooks to get onyx data, instead of using this directly. - * A valid use-case of this function is outside React components, like in utility functions. - */ -function getDraftComment(reportID: string): OnyxEntry | null | undefined { - return draftCommentCollection?.[ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT + reportID]; -} - -/** - * Returns true if the report has a valid draft comment. - * A valid draft comment is a non-empty string. - */ -function isValidDraftComment(comment?: string | null): boolean { - return !!comment; -} - -/** - * Returns true if the report has a valid draft comment. - */ -function hasValidDraftComment(reportID: string): boolean { - return isValidDraftComment(getDraftComment(reportID)); -} - -/** - * Prepares a draft comment by returning null if it's empty. - */ -function prepareDraftComment(comment: string | null) { - // logical OR is used to convert empty string to null - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - return comment || null; -} - -export {getDraftComment, isValidDraftComment, hasValidDraftComment, prepareDraftComment}; diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 779573b172a30..35d0972bcba13 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1502,7 +1502,7 @@ function getUserToInviteContactOption({ return userToInvite; } -function isValidReport(option: SearchOption, config: IsValidReportsConfig): boolean { +function isValidReport(option: SearchOption, config: IsValidReportsConfig, draftComment: string | undefined): boolean { const { betas = [], includeMultipleParticipantReports = false, @@ -1537,6 +1537,7 @@ function isValidReport(option: SearchOption, config: IsValidReportsConfi login: option.login, includeDomainEmail, isReportArchived: !!option.private_isArchived, + draftComment, }); if (!shouldBeInOptionList) { @@ -1753,6 +1754,7 @@ function getRestrictedLogins(config: GetOptionsConfig, options: OptionList, canS */ function getValidOptions( options: OptionList, + draftComments: OnyxCollection | undefined, { excludeLogins = {}, includeSelectedOptions = false, @@ -1804,7 +1806,7 @@ function getValidOptions( const filteringFunction = (report: SearchOption) => { let searchText = `${report.text ?? ''}${report.login ?? ''}`; - + const draftComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]; if (report.isThread) { searchText += report.alternateText ?? ''; } else if (report.isChatRoom) { @@ -1819,12 +1821,16 @@ function getValidOptions( return false; } - return isValidReport(report, { - ...getValidReportsConfig, - includeP2P, - includeDomainEmail, - loginsToExclude, - }); + return isValidReport( + report, + { + ...getValidReportsConfig, + includeP2P, + includeDomainEmail, + loginsToExclude, + }, + draftComment, + ); }; filteredReports = optionsOrderBy(options.reports, recentReportComparator, maxRecentReportElements ?? maxElements, filteringFunction); @@ -1927,25 +1933,40 @@ function getValidOptions( }; } +type SearchOptionsConfig = { + options: OptionList; + draftComments: OnyxCollection; + betas?: Beta[]; + isUsedInChatFinder?: boolean; + includeReadOnly?: boolean; + searchQuery?: string; + maxResults?: number; + includeUserToInvite?: boolean; + includeRecentReports?: boolean; + includeCurrentUser?: boolean; + shouldShowGBR?: boolean; +}; + /** * Build the options for the Search view */ -function getSearchOptions( - options: OptionList, - betas: Beta[] = [], +function getSearchOptions({ + options, + draftComments, + betas, isUsedInChatFinder = true, includeReadOnly = true, searchQuery = '', - maxResults?: number, - includeUserToInvite?: boolean, + maxResults, + includeUserToInvite, includeRecentReports = true, includeCurrentUser = false, shouldShowGBR = false, -): Options { +}: SearchOptionsConfig): Options { Timing.start(CONST.TIMING.LOAD_SEARCH_OPTIONS); Performance.markStart(CONST.TIMING.LOAD_SEARCH_OPTIONS); - const optionList = getValidOptions(options, { + const optionList = getValidOptions(options, draftComments, { betas, includeRecentReports, includeMultipleParticipantReports: true, @@ -1972,8 +1993,8 @@ function getSearchOptions( return optionList; } -function getShareLogOptions(options: OptionList, betas: Beta[] = [], searchString = '', maxElements?: number, includeUserToInvite = false): Options { - return getValidOptions(options, { +function getShareLogOptions(options: OptionList, draftComments: OnyxCollection, betas: Beta[] = [], searchString = '', maxElements?: number, includeUserToInvite = false): Options { + return getValidOptions(options, draftComments, { betas, includeMultipleParticipantReports: true, includeP2P: true, @@ -2018,6 +2039,7 @@ function getAttendeeOptions( betas: OnyxEntry, attendees: Attendee[], recentAttendees: Attendee[], + draftComments: OnyxCollection, includeOwnedWorkspaceChats = false, includeP2P = true, includeInvoiceRooms = false, @@ -2051,22 +2073,19 @@ function getAttendeeOptions( })) .map((attendee) => getParticipantsOption(attendee, personalDetailList as never)); - return getValidOptions( - {reports, personalDetails}, - { - betas, - selectedOptions: attendees.map((attendee) => ({...attendee, login: attendee.email})), - excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, - includeOwnedWorkspaceChats, - includeRecentReports: false, - includeP2P, - includeSelectedOptions: false, - includeSelfDM: false, - includeInvoiceRooms, - action, - recentAttendees: filteredRecentAttendees, - }, - ); + return getValidOptions({reports, personalDetails}, draftComments, { + betas, + selectedOptions: attendees.map((attendee) => ({...attendee, login: attendee.email})), + excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, + includeOwnedWorkspaceChats, + includeRecentReports: false, + includeP2P, + includeSelectedOptions: false, + includeSelfDM: false, + includeInvoiceRooms, + action, + recentAttendees: filteredRecentAttendees, + }); } /** @@ -2104,23 +2123,16 @@ function getMemberInviteOptions( betas: Beta[] = [], excludeLogins: Record = {}, includeSelectedOptions = false, - reports: Array> = [], - includeRecentReports = false, - searchString = '', - maxElements?: number, ): Options { - return getValidOptions( - {reports, personalDetails}, - { - betas, - includeP2P: true, - excludeLogins, - includeSelectedOptions, - includeRecentReports, - searchString, - maxElements, - }, - ); + return getValidOptions({personalDetails, reports: []}, undefined, { + betas, + includeP2P: true, + excludeLogins, + includeSelectedOptions, + includeRecentReports: false, + searchString: '', + maxElements: undefined, + }); } /** diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4d868cc859f17..b48fbba58f5ae 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -94,7 +94,6 @@ import type {OnboardingCompanySize, OnboardingMessage, OnboardingPurpose, Onboar import type {AddCommentOrAttachmentParams} from './API/parameters'; import {convertToDisplayString} from './CurrencyUtils'; import DateUtils from './DateUtils'; -import {hasValidDraftComment} from './DraftCommentUtils'; import {getEnvironmentURL} from './Environment/Environment'; import getEnvironment from './Environment/getEnvironment'; import type EnvironmentType from './Environment/getEnvironment/types'; @@ -8631,6 +8630,7 @@ type ShouldReportBeInOptionListParams = { login?: string; includeDomainEmail?: boolean; isReportArchived?: boolean; + draftComment: string | undefined; }; function reasonForReportToBeInOptionList({ @@ -8641,6 +8641,7 @@ function reasonForReportToBeInOptionList({ betas, excludeEmptyChats, doesReportHaveViolations, + draftComment, includeSelfDM = false, login, includeDomainEmail = false, @@ -8712,12 +8713,9 @@ function reasonForReportToBeInOptionList({ return CONST.REPORT_IN_LHN_REASONS.IS_FOCUSED; } - // Retrieve the draft comment for the report and convert it to a boolean - const hasDraftComment = hasValidDraftComment(report.reportID); - // Include reports that are relevant to the user in any view mode. Criteria include having a draft or having a GBR showing. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (hasDraftComment) { + if (draftComment) { return CONST.REPORT_IN_LHN_REASONS.HAS_DRAFT_COMMENT; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 989d480bbc4e5..71248551e5c73 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -13,7 +13,6 @@ import type Policy from '@src/types/onyx/Policy'; import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; -import {hasValidDraftComment} from './DraftCommentUtils'; import {translateLocal} from './Localize'; import {getLastActorDisplayName, getLastMessageTextForReport, getPersonalDetailsForAccountIDs, shouldShowLastActorDisplayName} from './OptionsListUtils'; import Parser from './Parser'; @@ -162,6 +161,7 @@ function shouldDisplayReportInLHN( isInFocusMode: boolean, betas: OnyxEntry, transactionViolations: OnyxCollection, + draftComment: OnyxEntry, isReportArchived?: boolean, reportAttributes?: ReportAttributesDerivedValue['reports'], ) { @@ -194,7 +194,7 @@ function shouldDisplayReportInLHN( // Check if report should override hidden status const isSystemChat = isSystemChatUtil(report); const shouldOverrideHidden = - hasValidDraftComment(report.reportID) || hasErrorsOtherThanFailedReceipt || isFocused || isSystemChat || !!report.isPinned || reportAttributes?.[report?.reportID]?.requiresAttention; + !!draftComment || hasErrorsOtherThanFailedReceipt || isFocused || isSystemChat || !!report.isPinned || reportAttributes?.[report?.reportID]?.requiresAttention; if (isHidden && !shouldOverrideHidden) { return {shouldDisplay: false}; @@ -209,6 +209,7 @@ function shouldDisplayReportInLHN( betas, excludeEmptyChats: true, doesReportHaveViolations, + draftComment, includeSelfDM: true, isReportArchived, }); @@ -222,6 +223,7 @@ function getReportsToDisplayInLHN( betas: OnyxEntry, policies: OnyxCollection, priorityMode: OnyxEntry, + draftComments: OnyxCollection, transactionViolations: OnyxCollection, reportNameValuePairs?: OnyxCollection, reportAttributes?: ReportAttributesDerivedValue['reports'], @@ -235,6 +237,8 @@ function getReportsToDisplayInLHN( return; } + const reportDraftComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]; + const {shouldDisplay, hasErrorsOtherThanFailedReceipt} = shouldDisplayReportInLHN( report, reports, @@ -242,6 +246,7 @@ function getReportsToDisplayInLHN( isInFocusMode, betas, transactionViolations, + reportDraftComment, isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`]), reportAttributes, ); @@ -254,18 +259,31 @@ function getReportsToDisplayInLHN( return reportsToDisplay; } -function updateReportsToDisplayInLHN( - displayedReports: ReportsToDisplayInLHN, - reports: OnyxCollection, - updatedReportsKeys: string[], - currentReportId: string | undefined, - isInFocusMode: boolean, - betas: OnyxEntry, - policies: OnyxCollection, - transactionViolations: OnyxCollection, - reportNameValuePairs?: OnyxCollection, - reportAttributes?: ReportAttributesDerivedValue['reports'], -) { +type UpdateReportsToDisplayInLHNProps = { + displayedReports: ReportsToDisplayInLHN; + reports: OnyxCollection; + updatedReportsKeys: string[]; + currentReportId: string | undefined; + isInFocusMode: boolean; + betas: OnyxEntry; + transactionViolations: OnyxCollection; + reportNameValuePairs?: OnyxCollection; + reportAttributes?: ReportAttributesDerivedValue['reports']; + draftComments: OnyxCollection; +}; + +function updateReportsToDisplayInLHN({ + displayedReports, + reports, + updatedReportsKeys, + currentReportId, + isInFocusMode, + betas, + transactionViolations, + reportNameValuePairs, + reportAttributes, + draftComments, +}: UpdateReportsToDisplayInLHNProps) { const displayedReportsCopy = {...displayedReports}; updatedReportsKeys.forEach((reportID) => { const report = reports?.[reportID]; @@ -273,6 +291,10 @@ function updateReportsToDisplayInLHN( return; } + // Get the specific draft comment for this report instead of using a single draft comment for all reports + // This fixes the issue where the current report's draft comment was incorrectly used to filter all reports + const reportDraftComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]; + const {shouldDisplay, hasErrorsOtherThanFailedReceipt} = shouldDisplayReportInLHN( report, reports, @@ -280,7 +302,8 @@ function updateReportsToDisplayInLHN( isInFocusMode, betas, transactionViolations, - isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`]), + reportDraftComment, + isArchivedReport(reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`] ?? {}), reportAttributes, ); @@ -298,6 +321,7 @@ function updateReportsToDisplayInLHN( */ function categorizeReportsForLHN( reportsToDisplay: ReportsToDisplayInLHN, + reportsDrafts: OnyxCollection | undefined, reportNameValuePairs?: OnyxCollection, reportAttributes?: ReportAttributesDerivedValue['reports'], ) { @@ -334,7 +358,8 @@ function categorizeReportsForLHN( const isPinned = !!report.isPinned; const requiresAttention = !!reportAttributes?.[reportID]?.requiresAttention; - const hasDraft = reportID ? hasValidDraftComment(reportID) : false; + const draftComment = reportsDrafts?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]; + const hasDraft = !!draftComment; const reportNameValuePairsKey = `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`; const rNVPs = reportNameValuePairs?.[reportNameValuePairsKey]; const isArchived = isArchivedNonExpenseReport(report, !!rNVPs?.private_isArchived); @@ -458,6 +483,7 @@ function sortReportsToDisplayInLHN( reportsToDisplay: ReportsToDisplayInLHN, priorityMode: OnyxEntry, localeCompare: LocaleContextProps['localeCompare'], + reportsDrafts: OnyxCollection | undefined, reportNameValuePairs?: OnyxCollection, reportAttributes?: ReportAttributesDerivedValue['reports'], ): string[] { @@ -477,7 +503,7 @@ function sortReportsToDisplayInLHN( // - Sorted by reportDisplayName in GSD (focus) view mode // Step 1: Categorize reports - const categories = categorizeReportsForLHN(reportsToDisplay, reportNameValuePairs, reportAttributes); + const categories = categorizeReportsForLHN(reportsToDisplay, reportsDrafts, reportNameValuePairs, reportAttributes); // Step 2: Sort each category const sortedCategories = sortCategorizedReports(categories, isInDefaultMode, localeCompare); diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index 16c76ba2860ef..570c4b322ce03 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -37,7 +37,16 @@ Onyx.connect({ }, }); -function getUnreadReportsForUnreadIndicator(reports: OnyxCollection, currentReportID: string | undefined) { +let allDraftComments: OnyxCollection = {}; +Onyx.connectWithoutView({ + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + waitForCollectionCallback: true, + callback: (value) => { + allDraftComments = value; + }, +}); + +function getUnreadReportsForUnreadIndicator(reports: OnyxCollection, currentReportID: string | undefined, draftComment: string | undefined) { return Object.values(reports ?? {}).filter((report) => { const notificationPreference = ReportUtils.getReportNotificationPreference(report); const chatReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`]; @@ -56,6 +65,7 @@ function getUnreadReportsForUnreadIndicator(reports: OnyxCollection, cur isInFocusMode: false, excludeEmptyChats: false, isReportArchived, + draftComment, }) && /** * Chats with hidden preference remain invisible in the LHN and are not considered "unread." @@ -71,13 +81,13 @@ function getUnreadReportsForUnreadIndicator(reports: OnyxCollection, cur }); } -const memoizedGetUnreadReportsForUnreadIndicator = memoize(getUnreadReportsForUnreadIndicator, {maxArgs: 1}); +const memoizedGetUnreadReportsForUnreadIndicator = memoize(getUnreadReportsForUnreadIndicator, {maxArgs: 3}); const triggerUnreadUpdate = debounce(() => { const currentReportID = navigationRef?.isReady?.() ? Navigation.getTopmostReportId() : undefined; - + const draftComment = allDraftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${currentReportID}`]; // We want to keep notification count consistent with what can be accessed from the LHN list - const unreadReports = memoizedGetUnreadReportsForUnreadIndicator(allReports, currentReportID); + const unreadReports = memoizedGetUnreadReportsForUnreadIndicator(allReports, currentReportID, draftComment); updateUnread(unreadReports.length); }, CONST.TIMING.UNREAD_UPDATE_DEBOUNCE_TIME); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 48d4ac585d2ad..2846c48beb0d9 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -64,7 +64,6 @@ import * as ApiUtils from '@libs/ApiUtils'; import * as CollectionUtils from '@libs/CollectionUtils'; import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types'; import DateUtils from '@libs/DateUtils'; -import {prepareDraftComment} from '@libs/DraftCommentUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; import * as Environment from '@libs/Environment/Environment'; import {getOldDotURLFromEnvironment} from '@libs/Environment/Environment'; @@ -1875,7 +1874,8 @@ function saveReportDraft(reportID: string, report: Report) { * When empty string or null is passed, it will delete the draft comment from Onyx store. */ function saveReportDraftComment(reportID: string, comment: string | null, callback: () => void = () => {}) { - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, prepareDraftComment(comment)).then(callback); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, comment || null).then(callback); } /** Broadcasts whether or not a user is typing on a report over the report's private pusher channel. */ diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index a252cc800e14c..32465c772cb30 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -67,6 +67,7 @@ function DebugReportPage({ }, [reportAttributesSelector], ); + const [draftComment] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, {canBeMissing: true}); const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {canBeMissing: true}); const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); const transactionID = DebugUtils.getTransactionID(report, reportActions); @@ -102,6 +103,7 @@ function DebugReportPage({ hasRBR, isReportArchived, isInFocusMode: priorityMode === CONST.PRIORITY_MODE.GSD, + draftComment, }); return [ @@ -147,7 +149,7 @@ function DebugReportPage({ : undefined, }, ]; - }, [report, transactionViolations, reportID, isReportArchived, chatReport, reportActions, transactions, reportAttributes?.reportErrors, betas, priorityMode, translate]); + }, [report, transactionViolations, reportID, isReportArchived, chatReport, reportActions, transactions, reportAttributes?.reportErrors, betas, priorityMode, draftComment, translate]); const DebugDetailsTab = useCallback( () => ( diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 152e9c05c789b..b74354e8455cd 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -70,6 +70,7 @@ function useOptions() { const {options: listOptions, areOptionsInitialized} = useOptionsList({ shouldInitialize: didScreenTransitionEnd, }); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const defaultOptions = useMemo(() => { const filteredOptions = memoizedGetValidOptions( @@ -77,13 +78,14 @@ function useOptions() { reports: listOptions.reports ?? [], personalDetails: (listOptions.personalDetails ?? []).concat(contacts), }, + draftComments, { betas: betas ?? [], includeSelfDM: true, }, ); return filteredOptions; - }, [betas, listOptions.personalDetails, listOptions.reports, contacts]); + }, [listOptions.reports, listOptions.personalDetails, contacts, draftComments, betas]); const unselectedOptions = useMemo(() => filterSelectedOptions(defaultOptions, new Set(selectedOptions.map(({accountID}) => accountID))), [defaultOptions, selectedOptions]); diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx index ee3a5e0b2c8e1..258adf7eb905d 100644 --- a/src/pages/Share/ShareTab.tsx +++ b/src/pages/Share/ShareTab.tsx @@ -44,6 +44,7 @@ function ShareTab({ref}: ShareTabProps) { const [textInputValue, debouncedTextInputValue, setTextInputValue] = useDebouncedState(''); const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); const selectionListRef = useRef(null); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); useImperativeHandle(ref, () => ({ focus: selectionListRef.current?.focusTextInput, @@ -60,8 +61,17 @@ function ShareTab({ref}: ShareTabProps) { if (!areOptionsInitialized) { return defaultListOptions; } - return getSearchOptions(options, betas ?? [], false, false, textInputValue, 20, true); - }, [areOptionsInitialized, betas, options, textInputValue]); + return getSearchOptions({ + options, + draftComments, + betas: betas ?? [], + isUsedInChatFinder: false, + includeReadOnly: false, + searchQuery: textInputValue, + maxResults: 20, + includeUserToInvite: true, + }); + }, [areOptionsInitialized, betas, draftComments, options, textInputValue]); const recentReportsOptions = useMemo(() => { if (textInputValue.trim() === '') { diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index eb42e95d32eb4..5e4b0110b0409 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -34,7 +34,6 @@ import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import {forceClearInput} from '@libs/ComponentUtils'; import {canSkipTriggerHotkeys, findCommonSuffixLength, insertText, insertWhiteSpaceAtIndex} from '@libs/ComposerUtils'; import convertToLTRForComposer from '@libs/convertToLTRForComposer'; -import {getDraftComment} from '@libs/DraftCommentUtils'; import {containsOnlyEmojis, extractEmojis, getAddedEmojis, getPreferredSkinToneIndex, replaceAndExtractEmojis} from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import getPlatform from '@libs/getPlatform'; @@ -247,7 +246,7 @@ function ComposerWithSuggestions({ const mobileInputScrollPosition = useRef(0); const cursorPositionValue = useSharedValue({x: 0, y: 0}); const tag = useSharedValue(-1); - const draftComment = getDraftComment(reportID) ?? ''; + const [draftComment = ''] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, {canBeMissing: true}); const [value, setValue] = useState(() => { if (draftComment) { emojisPresentBefore.current = extractEmojis(draftComment); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index b1fafdfa9ed74..aec4bce172b35 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -34,7 +34,6 @@ import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import DomUtils from '@libs/DomUtils'; -import {getDraftComment} from '@libs/DraftCommentUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Performance from '@libs/Performance'; import {getLinkedTransactionID, isMoneyRequestAction} from '@libs/ReportActionsUtils'; @@ -147,6 +146,7 @@ function ReportActionCompose({ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {canBeMissing: true}); const [initialModalState] = useOnyx(ONYXKEYS.MODAL, {canBeMissing: true}); const [newParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, {canBeMissing: true}); + const [draftComment] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, {canBeMissing: true}); /** * Updates the Highlight state of the composer */ @@ -176,7 +176,6 @@ function ReportActionCompose({ }, [debouncedLowerIsScrollLikelyLayoutTriggered]); const [isCommentEmpty, setIsCommentEmpty] = useState(() => { - const draftComment = getDraftComment(reportID); return !draftComment || !!draftComment.match(CONST.REGEX.EMPTY_COMMENT); }); diff --git a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx index 9bd22767e7659..fd78964978665 100644 --- a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx @@ -62,6 +62,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType }); const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); useEffect(() => { searchInServer(debouncedSearchTerm.trim()); @@ -77,6 +78,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType reports: options.reports, personalDetails: options.personalDetails, }, + draftComments, { betas, excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, @@ -90,7 +92,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType ...optionList, ...orderedOptions, }; - }, [action, areOptionsInitialized, betas, didScreenTransitionEnd, options.personalDetails, options.reports]); + }, [action, areOptionsInitialized, betas, didScreenTransitionEnd, draftComments, options.personalDetails, options.reports]); const chatOptions = useMemo(() => { if (!areOptionsInitialized) { diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index ed16a598bd07e..9afedc7e1e752 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -68,6 +68,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: false}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); const [recentAttendees] = useOnyx(ONYXKEYS.NVP_RECENT_ATTENDEES, {canBeMissing: true}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const policy = usePolicy(activePolicyID); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const {options, areOptionsInitialized} = useOptionsList({ @@ -87,7 +88,18 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde if (!areOptionsInitialized || !didScreenTransitionEnd) { getEmptyOptions(); } - const optionList = getAttendeeOptions(options.reports, options.personalDetails, betas, attendees, recentAttendees ?? [], iouType === CONST.IOU.TYPE.SUBMIT, true, false, action); + const optionList = getAttendeeOptions( + options.reports, + options.personalDetails, + betas, + attendees, + recentAttendees ?? [], + draftComments, + iouType === CONST.IOU.TYPE.SUBMIT, + true, + false, + action, + ); if (isPaidGroupPolicy) { const orderedOptions = orderOptions(optionList, searchTerm, { preferChatRoomsOverThreads: true, @@ -98,7 +110,20 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde optionList.personalDetails = orderedOptions.personalDetails; } return optionList; - }, [areOptionsInitialized, didScreenTransitionEnd, options.reports, options.personalDetails, betas, attendees, recentAttendees, iouType, action, isPaidGroupPolicy, searchTerm]); + }, [ + areOptionsInitialized, + didScreenTransitionEnd, + options.reports, + options.personalDetails, + betas, + attendees, + recentAttendees, + draftComments, + iouType, + action, + isPaidGroupPolicy, + searchTerm, + ]); const chatOptions = useMemo(() => { if (!areOptionsInitialized) { diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index a14a169ea1703..4a1109b9ed45c 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -130,6 +130,7 @@ function MoneyRequestParticipantsSelector({ shouldInitialize: didScreenTransitionEnd, }); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const [textInputAutoFocus, setTextInputAutoFocus] = useState(!isNative); const selectionListRef = useRef(null); @@ -176,6 +177,7 @@ function MoneyRequestParticipantsSelector({ reports: options.reports, personalDetails: options.personalDetails.concat(contacts), }, + draftComments, { betas, selectedOptions: participants as Participant[], @@ -208,16 +210,17 @@ function MoneyRequestParticipantsSelector({ ...orderedOptions, }; }, [ - action, - contacts, areOptionsInitialized, - betas, didScreenTransitionEnd, - iouType, - isCategorizeOrShareAction, - options.personalDetails, options.reports, + options.personalDetails, + contacts, + draftComments, + betas, participants, + iouType, + action, + isCategorizeOrShareAction, isPerDiemRequest, canShowManagerMcTest, isCorporateCardTransaction, diff --git a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx index 3bd87aee458c0..0d0193f6c2ed2 100644 --- a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx +++ b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx @@ -27,6 +27,7 @@ function BaseShareLogList({onAttachLogToReport}: BaseShareLogListProps) { const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const {options, areOptionsInitialized} = useOptionsList(); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const defaultOptions = useMemo(() => { if (!areOptionsInitialized) { @@ -38,7 +39,7 @@ function BaseShareLogList({onAttachLogToReport}: BaseShareLogListProps) { headerMessage: '', }; } - const shareLogOptions = getShareLogOptions(options, betas ?? []); + const shareLogOptions = getShareLogOptions(options, draftComments, betas ?? []); const header = getHeaderMessage((shareLogOptions.recentReports.length || 0) + (shareLogOptions.personalDetails.length || 0) !== 0, !!shareLogOptions.userToInvite, ''); @@ -46,7 +47,7 @@ function BaseShareLogList({onAttachLogToReport}: BaseShareLogListProps) { ...shareLogOptions, headerMessage: header, }; - }, [areOptionsInitialized, options, betas]); + }, [areOptionsInitialized, options, draftComments, betas]); const searchOptions = useMemo(() => { if (debouncedSearchValue.trim() === '') { diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index c89572b760a91..a3ac65331d14a 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -32,6 +32,7 @@ function useOptions() { const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true}); const currentVacationDelegate = vacationDelegate?.delegate; const delegatePersonalDetails = getPersonalDetailByEmail(currentVacationDelegate ?? ''); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const excludeLogins = useMemo( () => ({ @@ -47,6 +48,7 @@ function useOptions() { reports: optionsList.reports, personalDetails: optionsList.personalDetails, }, + draftComments, { betas, excludeLogins, @@ -62,7 +64,7 @@ function useOptions() { currentUserOption, headerMessage, }; - }, [optionsList.reports, optionsList.personalDetails, betas, excludeLogins]); + }, [optionsList.reports, optionsList.personalDetails, draftComments, betas, excludeLogins]); const options = useMemo(() => { const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), countryCode, { diff --git a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx index 3673f63c329a9..64d88f4552391 100644 --- a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx +++ b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx @@ -29,6 +29,7 @@ function useOptions() { const {options: optionsList, areOptionsInitialized} = useOptionsList(); const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const existingDelegates = useMemo( () => account?.delegatedAccess?.delegates?.reduce( @@ -48,6 +49,7 @@ function useOptions() { reports: optionsList.reports, personalDetails: optionsList.personalDetails, }, + draftComments, { betas, excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT, ...existingDelegates}, @@ -68,7 +70,7 @@ function useOptions() { currentUserOption, headerMessage, }; - }, [optionsList.reports, optionsList.personalDetails, betas, existingDelegates, isLoading]); + }, [optionsList.reports, optionsList.personalDetails, draftComments, betas, existingDelegates, isLoading]); const options = useMemo(() => { const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), countryCode, { diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 404618b208129..ed3f234c67127 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -44,6 +44,7 @@ function useOptions() { const {options: optionsList, areOptionsInitialized} = useOptionsList(); const session = useSession(); const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const defaultOptions = useMemo(() => { const {recentReports, personalDetails, userToInvite, currentUserOption} = memoizedGetValidOptions( @@ -51,6 +52,7 @@ function useOptions() { reports: optionsList.reports, personalDetails: optionsList.personalDetails, }, + draftComments, { betas, excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, @@ -72,7 +74,7 @@ function useOptions() { currentUserOption, headerMessage, }; - }, [optionsList.reports, optionsList.personalDetails, betas, isLoading]); + }, [optionsList.reports, optionsList.personalDetails, draftComments, betas, isLoading]); const optionsWithoutCurrentUser = useMemo(() => { if (!session?.accountID) { diff --git a/tests/perf-test/OptionsListUtils.perf-test.ts b/tests/perf-test/OptionsListUtils.perf-test.ts index 9b44bcdaa2fb7..345b11ecd6ff1 100644 --- a/tests/perf-test/OptionsListUtils.perf-test.ts +++ b/tests/perf-test/OptionsListUtils.perf-test.ts @@ -107,26 +107,26 @@ describe('OptionsListUtils', () => { /* Testing getSearchOptions */ test('[OptionsListUtils] getSearchOptions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => getSearchOptions(options, mockedBetas)); + await measureFunction(() => getSearchOptions({options, betas: mockedBetas, draftComments: {}})); }); /* Testing getShareLogOptions */ test('[OptionsListUtils] getShareLogOptions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => getShareLogOptions(options, mockedBetas)); + await measureFunction(() => getShareLogOptions(options, {}, mockedBetas)); }); /* Testing getFilteredOptions */ test('[OptionsListUtils] getFilteredOptions with search value', async () => { await waitForBatchedUpdates(); - const formattedOptions = getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, ValidOptionsConfig); + const formattedOptions = getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {}, ValidOptionsConfig); await measureFunction(() => { filterAndOrderOptions(formattedOptions, SEARCH_VALUE, COUNTRY_CODE); }); }); test('[OptionsListUtils] getFilteredOptions with empty search value', async () => { await waitForBatchedUpdates(); - const formattedOptions = getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, ValidOptionsConfig); + const formattedOptions = getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {}, ValidOptionsConfig); await measureFunction(() => { filterAndOrderOptions(formattedOptions, '', COUNTRY_CODE); }); @@ -138,6 +138,7 @@ describe('OptionsListUtils', () => { await measureFunction(() => getValidOptions( {reports: options.reports, personalDetails: options.personalDetails}, + {}, { betas: mockedBetas, includeMultipleParticipantReports: true, diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts index 2ea0b7367b11e..992d0ac08104f 100644 --- a/tests/perf-test/ReportUtils.perf-test.ts +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -165,7 +165,9 @@ describe('ReportUtils', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS]; await waitForBatchedUpdates(); - await measureFunction(() => shouldReportBeInOptionList({report, chatReport, currentReportId, isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false})); + await measureFunction(() => + shouldReportBeInOptionList({report, chatReport, currentReportId, isInFocusMode, betas, doesReportHaveViolations: false, excludeEmptyChats: false, draftComment: undefined}), + ); }); test('[ReportUtils] getWorkspaceIcon on 1k policies', async () => { diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 50610542f9d7b..3951852237435 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -94,11 +94,11 @@ describe('SidebarUtils', () => { test('[SidebarUtils] getReportsToDisplayInLHN on 15k reports for default priorityMode', async () => { await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getReportsToDisplayInLHN(currentReportId, allReports, mockedBetas, policies, CONST.PRIORITY_MODE.DEFAULT, transactionViolations)); + await measureFunction(() => SidebarUtils.getReportsToDisplayInLHN(currentReportId, allReports, mockedBetas, policies, CONST.PRIORITY_MODE.DEFAULT, {}, transactionViolations)); }); test('[SidebarUtils] getReportsToDisplayInLHN on 15k reports for GSD priorityMode', async () => { await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getReportsToDisplayInLHN(currentReportId, allReports, mockedBetas, policies, CONST.PRIORITY_MODE.GSD, transactionViolations)); + await measureFunction(() => SidebarUtils.getReportsToDisplayInLHN(currentReportId, allReports, mockedBetas, policies, CONST.PRIORITY_MODE.GSD, {}, transactionViolations)); }); }); diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index c3346c7269225..a5d74f049d784 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -735,12 +735,12 @@ describe('DebugUtils', () => { Onyx.clear(); }); it('returns null when report is not defined', () => { - const reason = DebugUtils.getReasonForShowingRowInLHN({report: undefined, chatReport: chatReportR14932, doesReportHaveViolations: false}); + const reason = DebugUtils.getReasonForShowingRowInLHN({report: undefined, chatReport: chatReportR14932, doesReportHaveViolations: false, draftComment: ''}); expect(reason).toBeNull(); }); it('returns correct reason when report has a valid draft comment', async () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}1`, 'Hello world!'); - const reason = DebugUtils.getReasonForShowingRowInLHN({report: baseReport, chatReport: chatReportR14932, doesReportHaveViolations: false}); + const reason = DebugUtils.getReasonForShowingRowInLHN({report: baseReport, chatReport: chatReportR14932, doesReportHaveViolations: false, draftComment: 'Hello world!'}); expect(reason).toBe('debug.reasonVisibleInLHN.hasDraftComment'); }); it('returns correct reason when report has GBR', () => { @@ -752,6 +752,7 @@ describe('DebugUtils', () => { }, chatReport: chatReportR14932, doesReportHaveViolations: false, + draftComment: '', }); expect(reason).toBe('debug.reasonVisibleInLHN.hasGBR'); }); @@ -763,6 +764,7 @@ describe('DebugUtils', () => { }, chatReport: chatReportR14932, doesReportHaveViolations: false, + draftComment: '', }); expect(reason).toBe('debug.reasonVisibleInLHN.pinnedByUser'); }); @@ -778,6 +780,7 @@ describe('DebugUtils', () => { }, chatReport: chatReportR14932, doesReportHaveViolations: false, + draftComment: '', }); expect(reason).toBe('debug.reasonVisibleInLHN.hasAddWorkspaceRoomErrors'); }); @@ -801,6 +804,7 @@ describe('DebugUtils', () => { chatReport: chatReportR14932, isInFocusMode: true, doesReportHaveViolations: false, + draftComment: '', }); expect(reason).toBe('debug.reasonVisibleInLHN.isUnread'); }); @@ -818,6 +822,7 @@ describe('DebugUtils', () => { hasRBR: false, isReportArchived: isReportArchived.current, doesReportHaveViolations: false, + draftComment: '', }); expect(reason).toBe('debug.reasonVisibleInLHN.isArchived'); }); @@ -829,6 +834,7 @@ describe('DebugUtils', () => { }, chatReport: chatReportR14932, doesReportHaveViolations: false, + draftComment: '', }); expect(reason).toBe('debug.reasonVisibleInLHN.isSelfDM'); }); @@ -837,6 +843,7 @@ describe('DebugUtils', () => { report: baseReport, chatReport: chatReportR14932, doesReportHaveViolations: false, + draftComment: '', }); expect(reason).toBe('debug.reasonVisibleInLHN.isFocused'); }); @@ -889,7 +896,13 @@ describe('DebugUtils', () => { reportID: '1', }, }); - const reason = DebugUtils.getReasonForShowingRowInLHN({report: MOCK_TRANSACTION_REPORT, chatReport: chatReportR14932, hasRBR: true, doesReportHaveViolations: true}); + const reason = DebugUtils.getReasonForShowingRowInLHN({ + report: MOCK_TRANSACTION_REPORT, + chatReport: chatReportR14932, + hasRBR: true, + doesReportHaveViolations: true, + draftComment: '', + }); expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); }); it('returns correct reason when report has violations', async () => { @@ -941,11 +954,17 @@ describe('DebugUtils', () => { reportID: '1', }, }); - const reason = DebugUtils.getReasonForShowingRowInLHN({report: MOCK_EXPENSE_REPORT, chatReport: chatReportR14932, hasRBR: true, doesReportHaveViolations: true}); + const reason = DebugUtils.getReasonForShowingRowInLHN({ + report: MOCK_EXPENSE_REPORT, + chatReport: chatReportR14932, + hasRBR: true, + doesReportHaveViolations: true, + draftComment: '', + }); expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); }); it('returns correct reason when report has errors', () => { - const reason = DebugUtils.getReasonForShowingRowInLHN({report: baseReport, chatReport: chatReportR14932, hasRBR: true, doesReportHaveViolations: false}); + const reason = DebugUtils.getReasonForShowingRowInLHN({report: baseReport, chatReport: chatReportR14932, hasRBR: true, doesReportHaveViolations: false, draftComment: ''}); expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); }); }); diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 858bce4fdfc23..f430bc19e4523 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -618,7 +618,7 @@ describe('OptionsListUtils', () => { it('should return all options when no search value is provided', () => { // Given a set of options // When we call getSearchOptions with all betas - const results = getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); + const results = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // Then all personal details (including those that have reports) should be returned expect(results.personalDetails.length).toBe(10); @@ -630,7 +630,18 @@ describe('OptionsListUtils', () => { it('should include current user when includeCurrentUser is true for type:chat from suggestions', () => { // Given a set of options where the current user is Iron Man (accountID: 2) // When we call getSearchOptions with includeCurrentUser set to true - const results = getSearchOptions(OPTIONS, [CONST.BETAS.ALL], true, true, '', undefined, false, true, true); + const results = getSearchOptions({ + options: OPTIONS, + draftComments: {}, + betas: [CONST.BETAS.ALL], + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: '', + maxResults: undefined, + includeUserToInvite: false, + includeRecentReports: true, + includeCurrentUser: true, + }); // Then the current user should be included in personalDetails const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); @@ -645,7 +656,17 @@ describe('OptionsListUtils', () => { it('should exclude current user when includeCurrentUser is false', () => { // Given a set of options where the current user is Iron Man (accountID: 2) // When we call getSearchOptions with includeCurrentUser set to false (default behavior) - const results = getSearchOptions(OPTIONS, [CONST.BETAS.ALL], true, true, '', undefined, false, true, false); + const results = getSearchOptions({ + options: OPTIONS, + draftComments: {}, + betas: [CONST.BETAS.ALL], + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: '', + maxResults: undefined, + includeUserToInvite: false, + includeRecentReports: true, + }); // Then the current user should not be included in personalDetails const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); @@ -660,10 +681,13 @@ describe('OptionsListUtils', () => { it('should sort options alphabetically and preserves reportID for personal details with existing reports', () => { // Given a set of reports and personalDetails // When we call getValidOptions() - let results: Pick = getValidOptions({ - reports: OPTIONS.reports, - personalDetails: OPTIONS.personalDetails, - }); + let results: Pick = getValidOptions( + { + reports: OPTIONS.reports, + personalDetails: OPTIONS.personalDetails, + }, + {}, + ); // When we call orderOptions() results = orderOptions(results); @@ -694,7 +718,7 @@ describe('OptionsListUtils', () => { it('should sort personal details options alphabetically when only personal details are provided', () => { // Given a set of personalDetails and an empty reports array - let results: Pick = getValidOptions({personalDetails: OPTIONS.personalDetails, reports: []}); + let results: Pick = getValidOptions({personalDetails: OPTIONS.personalDetails, reports: []}, {}); // When we call orderOptions() results = orderOptions(results); @@ -721,7 +745,7 @@ describe('OptionsListUtils', () => { it('should return empty options when no reports or personal details are provided', () => { // Given empty arrays of reports and personalDetails // When we call getValidOptions() - const results = getValidOptions({reports: [], personalDetails: []}); + const results = getValidOptions({reports: [], personalDetails: []}, {}); // Then the result should be empty expect(results.personalDetails).toEqual([]); @@ -735,7 +759,7 @@ describe('OptionsListUtils', () => { it('should include Concierge by default in results', () => { // Given a set of reports and personalDetails that includes Concierge // When we call getValidOptions() - const results = getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}); + const results = getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}, {}); // Then the result should include all personalDetails except the currently logged in user expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 1); @@ -751,6 +775,7 @@ describe('OptionsListUtils', () => { reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, }, + {}, { excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, }, @@ -765,7 +790,11 @@ describe('OptionsListUtils', () => { it('should exclude Chronos when excludedLogins is specified', () => { // Given a set of reports and personalDetails that includes Chronos and a config object that excludes Chronos // When we call getValidOptions() - const results = getValidOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}); + const results = getValidOptions( + {reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, + {}, + {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, + ); // Then the result should include all personalDetails except the currently logged in user and Chronos expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); @@ -781,6 +810,7 @@ describe('OptionsListUtils', () => { reports: OPTIONS_WITH_RECEIPTS.reports, personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, }, + {}, { excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, }, @@ -797,6 +827,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions() const result = getValidOptions( {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, + {}, {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, ); @@ -811,6 +842,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions() const result = getValidOptions( {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, + {}, {includeP2P: true, canShowManagerMcTest: false, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, ); @@ -834,6 +866,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions() const optionsWhenUserAlreadySubmittedExpense = getValidOptions( {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, + {}, {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, ); @@ -881,6 +914,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions with includeMultipleParticipantReports set to true const results = getValidOptions( {reports: [adminRoom], personalDetails: OPTIONS.personalDetails}, + {}, { includeMultipleParticipantReports: true, }, @@ -929,6 +963,7 @@ describe('OptionsListUtils', () => { }; const results = getValidOptions( {reports: [workspaceChat], personalDetails: []}, + {}, { includeMultipleParticipantReports: true, showRBR: true, @@ -975,6 +1010,7 @@ describe('OptionsListUtils', () => { }; const results = getValidOptions( {reports: [workspaceChat], personalDetails: []}, + {}, { includeMultipleParticipantReports: true, showRBR: false, @@ -988,12 +1024,16 @@ describe('OptionsListUtils', () => { it('should include all reports by default', () => { // Given a set of reports and personalDetails that includes workspace rooms // When we call getValidOptions() - const results = getValidOptions(OPTIONS_WITH_WORKSPACE_ROOM, { - includeRecentReports: true, - includeMultipleParticipantReports: true, - includeP2P: true, - includeOwnedWorkspaceChats: true, - }); + const results = getValidOptions( + OPTIONS_WITH_WORKSPACE_ROOM, + {}, + { + includeRecentReports: true, + includeMultipleParticipantReports: true, + includeP2P: true, + includeOwnedWorkspaceChats: true, + }, + ); // Then the result should include all reports except the currently logged in user expect(results.recentReports.length).toBe(OPTIONS_WITH_WORKSPACE_ROOM.reports.length - 1); @@ -1005,7 +1045,7 @@ describe('OptionsListUtils', () => { it('should exclude users with recent reports from personalDetails', () => { // Given a set of reports and personalDetails // When we call getValidOptions with no search value - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); const reportLogins = results.recentReports.map((reportOption) => reportOption.login); const personalDetailsOverlapWithReports = results.personalDetails.every((personalDetailOption) => reportLogins.includes(personalDetailOption.login)); @@ -1018,7 +1058,7 @@ describe('OptionsListUtils', () => { it('should exclude selected options', () => { // Given a set of reports and personalDetails // When we call getValidOptions with excludeLogins param - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {excludeLogins: {'peterparker@expensify.com': true}}); + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {excludeLogins: {'peterparker@expensify.com': true}}); // Then the option should not appear anywhere in either list expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); @@ -1028,7 +1068,7 @@ describe('OptionsListUtils', () => { it('should include Concierge in the results by default', () => { // Given a set of report and personalDetails that include Concierge // When we call getValidOptions() - const results = getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}); + const results = getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}, {}); // Then the result should include all personalDetails except the currently logged in user expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 1); @@ -1044,6 +1084,7 @@ describe('OptionsListUtils', () => { reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, }, + {}, { excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, }, @@ -1059,7 +1100,11 @@ describe('OptionsListUtils', () => { it('should exclude Chronos from the results when it is specified in excludedLogins', () => { // given a set of reports and personalDetails that includes Chronos // When we call getValidOptions() with excludeLogins param - const results = getValidOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}); + const results = getValidOptions( + {reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, + {}, + {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, + ); // Then the result should include all personalDetails except the currently logged in user and Chronos expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); @@ -1076,6 +1121,7 @@ describe('OptionsListUtils', () => { reports: OPTIONS_WITH_RECEIPTS.reports, personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, }, + {}, { excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, }, @@ -1092,7 +1138,7 @@ describe('OptionsListUtils', () => { // Given a set of reports and personalDetails with multiple reports // When we call getValidOptions with maxRecentReportElements set to 2 const maxRecentReports = 2; - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxRecentReportElements: maxRecentReports}); + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: maxRecentReports}); // Then the recent reports should be limited to the specified number expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); @@ -1101,8 +1147,8 @@ describe('OptionsListUtils', () => { it('should show all reports when maxRecentReportElements is not specified', () => { // Given a set of reports and personalDetails // When we call getValidOptions without maxRecentReportElements - const resultsWithoutLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxRecentReportElements: 2}); + const resultsWithoutLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); + const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); // Then the results without limit should have more or equal reports expect(resultsWithoutLimit.recentReports.length).toBeGreaterThanOrEqual(resultsWithLimit.recentReports.length); @@ -1111,8 +1157,8 @@ describe('OptionsListUtils', () => { it('should not affect personalDetails count when maxRecentReportElements is specified', () => { // Given a set of reports and personalDetails // When we call getValidOptions with and without maxRecentReportElements - const resultsWithoutLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxRecentReportElements: 2}); + const resultsWithoutLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); + const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); // Then personalDetails should remain the same regardless of maxRecentReportElements expect(resultsWithLimit.personalDetails.length).toBe(resultsWithoutLimit.personalDetails.length); @@ -1123,7 +1169,11 @@ describe('OptionsListUtils', () => { // When we call getValidOptions with both maxElements and maxRecentReportElements const maxRecentReports = 3; const maxTotalElements = 10; - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxElements: maxTotalElements, maxRecentReportElements: maxRecentReports}); + const results = getValidOptions( + {reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, + {}, + {maxElements: maxTotalElements, maxRecentReportElements: maxRecentReports}, + ); // Then recent reports should be limited by maxRecentReportElements expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); @@ -1146,6 +1196,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions for share destination with an empty search value const results = getValidOptions( {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, + {}, { betas: [], includeMultipleParticipantReports: true, @@ -1180,6 +1231,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions for share destination with an empty search value const results = getValidOptions( {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + {}, { betas: [], includeMultipleParticipantReports: true, @@ -1205,7 +1257,7 @@ describe('OptionsListUtils', () => { it('should not include read-only report', () => { // Given a list of 11 report options with reportID of 10 is archived // When we call getShareLogOptions - const results = getShareLogOptions(OPTIONS, []); + const results = getShareLogOptions(OPTIONS, {}, []); // Then the report with reportID of 10 should not be included on the list expect(results.recentReports.length).toBe(10); @@ -1265,7 +1317,7 @@ describe('OptionsListUtils', () => { it('should return all options when search is empty', () => { // Given a set of options // When we call getSearchOptions with all betas - const options = getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with an empty search value const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); @@ -1277,7 +1329,7 @@ describe('OptionsListUtils', () => { const searchText = 'man'; // Given a set of options // When we call getSearchOptions with all betas - const options = getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value and sortByReportTypeInSearch param const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {sortByReportTypeInSearch: true}); @@ -1296,7 +1348,7 @@ describe('OptionsListUtils', () => { const searchText = 'mistersinister@marauders.com'; // Given a set of options // When we call getSearchOptions with all betas - const options = getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); @@ -1310,7 +1362,7 @@ describe('OptionsListUtils', () => { const searchText = 'Archived'; // Given a set of options // When we call getSearchOptions with all betas - const options = getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); @@ -1326,7 +1378,7 @@ describe('OptionsListUtils', () => { // Given a set of options created from PERSONAL_DETAILS_WITH_PERIODS const OPTIONS_WITH_PERIODS = createOptionList(PERSONAL_DETAILS_WITH_PERIODS, REPORTS); // When we call getSearchOptions with all betas - const options = getSearchOptions(OPTIONS_WITH_PERIODS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS_WITH_PERIODS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value and sortByReportTypeInSearch param const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {sortByReportTypeInSearch: true}); @@ -1340,7 +1392,7 @@ describe('OptionsListUtils', () => { const searchText = 'avengers'; // Given a set of options with workspace rooms // When we call getSearchOptions with all betas - const options = getSearchOptions(OPTIONS_WITH_WORKSPACE_ROOM, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS_WITH_WORKSPACE_ROOM, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); @@ -1353,7 +1405,7 @@ describe('OptionsListUtils', () => { it('should put exact match by login on the top of the list', () => { const searchText = 'reedrichards@expensify.com'; // Given a set of options with all betas - const options = getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); @@ -1368,7 +1420,7 @@ describe('OptionsListUtils', () => { // Given a set of options with chat rooms const OPTIONS_WITH_CHAT_ROOMS = createOptionList(PERSONAL_DETAILS, REPORTS_WITH_CHAT_ROOM); // When we call getSearchOptions with all betas - const options = getSearchOptions(OPTIONS_WITH_CHAT_ROOMS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS_WITH_CHAT_ROOMS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); @@ -1382,7 +1434,7 @@ describe('OptionsListUtils', () => { renderLocaleContextProvider(); const searchText = 'fantastic'; // Given a set of options - const options = getSearchOptions(OPTIONS); + const options = getSearchOptions({options: OPTIONS, draftComments: {}}); // When we call filterAndOrderOptions with a search value const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); @@ -1397,7 +1449,7 @@ describe('OptionsListUtils', () => { it('should return the user to invite when the search value is a valid, non-existent email', () => { const searchText = 'test@email.com'; // Given a set of options - const options = getSearchOptions(OPTIONS); + const options = getSearchOptions({options: OPTIONS, draftComments: {}}); // When we call filterAndOrderOptions with a search value const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); @@ -1408,7 +1460,7 @@ describe('OptionsListUtils', () => { it('should not return any results if the search value is on an excluded logins list', () => { const searchText = 'admin@expensify.com'; // Given a set of options with excluded logins list - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); // When we call filterAndOrderOptions with a search value and excluded logins list const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); @@ -1419,7 +1471,7 @@ describe('OptionsListUtils', () => { it('should return the user to invite when the search value is a valid, non-existent email and the user is not excluded', () => { const searchText = 'test@email.com'; // Given a set of options - const options = getSearchOptions(OPTIONS); + const options = getSearchOptions({options: OPTIONS, draftComments: {}}); // When we call filterAndOrderOptions with a search value and excludeLogins const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); @@ -1430,7 +1482,7 @@ describe('OptionsListUtils', () => { it('should return limited amount of recent reports if the limit is set', () => { const searchText = ''; // Given a set of options - const options = getSearchOptions(OPTIONS); + const options = getSearchOptions({options: OPTIONS, draftComments: {}}); // When we call filterAndOrderOptions with a search value and maxRecentReportsToShow set to 2 const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {maxRecentReportsToShow: 2}); @@ -1448,7 +1500,7 @@ describe('OptionsListUtils', () => { it('should not return any user to invite if email exists on the personal details list', () => { const searchText = 'natasharomanoff@expensify.com'; // Given a set of options with all betas - const options = getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we call filterAndOrderOptions with a search value const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); @@ -1492,6 +1544,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions for share destination with the filteredReports const options = getValidOptions( {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, + {}, { betas: [], includeMultipleParticipantReports: true, @@ -1528,6 +1581,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions for share destination with the filteredReports const options = getValidOptions( {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + {}, { betas: [], includeMultipleParticipantReports: true, @@ -1564,6 +1618,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions for share destination with the filteredReports const options = getValidOptions( {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + {}, { betas: [], includeMultipleParticipantReports: true, @@ -1588,7 +1643,7 @@ describe('OptionsListUtils', () => { it('should show the option from personal details when searching for personal detail with no existing report', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that matches a personal detail with no existing report const filteredOptions = filterAndOrderOptions(options, 'hulk', COUNTRY_CODE); @@ -1602,7 +1657,7 @@ describe('OptionsListUtils', () => { it('should not return any options or user to invite if there are no search results and the string does not match a potential email or phone', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports const filteredOptions = filterAndOrderOptions(options, 'marc@expensify', COUNTRY_CODE); @@ -1615,7 +1670,7 @@ describe('OptionsListUtils', () => { it('should not return any options but should return an user to invite if no matching options exist and the search value is a potential email', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports const filteredOptions = filterAndOrderOptions(options, 'marc@expensify.com', COUNTRY_CODE); @@ -1628,7 +1683,7 @@ describe('OptionsListUtils', () => { it('should return user to invite when search term has a period with options for it that do not contain the period', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports but matches user to invite const filteredOptions = filterAndOrderOptions(options, 'peter.parker@expensify.com', COUNTRY_CODE); @@ -1640,7 +1695,7 @@ describe('OptionsListUtils', () => { it('should return user which has displayName with accent mark when search value without accent mark', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value without accent mark const filteredOptions = filterAndOrderOptions(options, 'Timothee', COUNTRY_CODE); @@ -1650,7 +1705,7 @@ describe('OptionsListUtils', () => { it('should not return options but should return an user to invite if no matching options exist and the search value is a potential phone number', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports but matches user to invite const filteredOptions = filterAndOrderOptions(options, '5005550006', COUNTRY_CODE); @@ -1665,7 +1720,7 @@ describe('OptionsListUtils', () => { it('should not return options but should return an user to invite if no matching options exist and the search value is a potential phone number with country code added', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports but matches user to invite const filteredOptions = filterAndOrderOptions(options, '+15005550006', COUNTRY_CODE); @@ -1680,7 +1735,7 @@ describe('OptionsListUtils', () => { it('should not return options but should return an user to invite if no matching options exist and the search value is a potential phone number with special characters added', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports but matches user to invite const filteredOptions = filterAndOrderOptions(options, '+1 (800)324-3233', COUNTRY_CODE); @@ -1695,7 +1750,7 @@ describe('OptionsListUtils', () => { it('should not return any options or user to invite if contact number contains alphabet characters', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports const filteredOptions = filterAndOrderOptions(options, '998243aaaa', COUNTRY_CODE); @@ -1708,7 +1763,7 @@ describe('OptionsListUtils', () => { it('should not return any options if search value does not match any personal details', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that does not match any personal details const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); @@ -1718,7 +1773,7 @@ describe('OptionsListUtils', () => { it('should return one recent report and no personal details if a search value provides an email', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that matches an email const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); @@ -1732,7 +1787,7 @@ describe('OptionsListUtils', () => { it('should return all matching reports and personal details', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); // When we call filterAndOrderOptions with a search value that matches both reports and personal details and maxRecentReportsToShow param const filteredOptions = filterAndOrderOptions(options, '.com', COUNTRY_CODE, {maxRecentReportsToShow: 5}); @@ -1749,7 +1804,7 @@ describe('OptionsListUtils', () => { it('should return matching option when searching (getSearchOptions)', () => { // Given a set of options - const options = getSearchOptions(OPTIONS); + const options = getSearchOptions({options: OPTIONS, draftComments: {}}); // When we call filterAndOrderOptions with a search value that matches a personal detail const filteredOptions = filterAndOrderOptions(options, 'spider', COUNTRY_CODE); @@ -1761,7 +1816,7 @@ describe('OptionsListUtils', () => { it('should return latest lastVisibleActionCreated item on top when search value matches multiple items (getSearchOptions)', () => { // Given a set of options - const options = getSearchOptions(OPTIONS); + const options = getSearchOptions({options: OPTIONS, draftComments: {}}); // When we call filterAndOrderOptions with a search value that matches multiple items const filteredOptions = filterAndOrderOptions(options, 'fantastic', COUNTRY_CODE); @@ -1778,7 +1833,7 @@ describe('OptionsListUtils', () => { // Given a set of options with periods const OPTIONS_WITH_PERIODS = createOptionList(PERSONAL_DETAILS_WITH_PERIODS, REPORTS); // When we call getSearchOptions - const results = getSearchOptions(OPTIONS_WITH_PERIODS); + const results = getSearchOptions({options: OPTIONS_WITH_PERIODS, draftComments: {}}); // When we pass the returned options to filterAndOrderOptions with a search value const filteredResults = filterAndOrderOptions(results, 'barry.allen@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); @@ -1796,7 +1851,7 @@ describe('OptionsListUtils', () => { OPTIONS.personalDetails = OPTIONS.personalDetails.flatMap((obj) => [obj, {...obj}]); // Given a set of options - const options = getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we call filterAndOrderOptions with a an empty search value const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); const matchingEntries = filteredOptions.personalDetails.filter((detail) => detail.login === login); @@ -1812,7 +1867,7 @@ describe('OptionsListUtils', () => { const OPTIONS_WITH_SELF_DM = createOptionList(PERSONAL_DETAILS, REPORTS_WITH_SELF_DM); // Given a set of options with self dm and all betas - const options = getSearchOptions(OPTIONS_WITH_SELF_DM, [CONST.BETAS.ALL]); + const options = getSearchOptions({options: OPTIONS_WITH_SELF_DM, draftComments: {}, betas: [CONST.BETAS.ALL]}); // When we call filterAndOrderOptions with a search value const filteredOptions = filterAndOrderOptions(options, searchTerm, COUNTRY_CODE); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 6222153a261e5..645df958b3cd6 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -3599,6 +3599,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeTruthy(); }); @@ -3650,6 +3651,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: true, excludeEmptyChats: false, + draftComment: '', }), ).toBeTruthy(); }); @@ -3671,6 +3673,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeTruthy(); }); @@ -3692,6 +3695,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: 'fake draft', }), ).toBeTruthy(); }); @@ -3713,6 +3717,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeTruthy(); }); @@ -3747,6 +3752,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeTruthy(); }); @@ -3777,6 +3783,7 @@ describe('ReportUtils', () => { doesReportHaveViolations: false, excludeEmptyChats: false, isReportArchived: isReportArchived.current, + draftComment: '', }), ).toBeTruthy(); }); @@ -3807,6 +3814,7 @@ describe('ReportUtils', () => { doesReportHaveViolations: false, excludeEmptyChats: false, isReportArchived: isReportArchived.current, + draftComment: '', }), ).toBeFalsy(); }); @@ -3830,6 +3838,7 @@ describe('ReportUtils', () => { doesReportHaveViolations: false, excludeEmptyChats: false, includeSelfDM, + draftComment: '', }), ).toBeTruthy(); }); @@ -3855,6 +3864,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeFalsy(); }); @@ -3873,6 +3883,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeFalsy(); }); @@ -3894,6 +3905,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeFalsy(); }); @@ -3935,6 +3947,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeFalsy(); }); @@ -3953,6 +3966,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: true, + draftComment: '', }), ).toBeFalsy(); }); @@ -3973,6 +3987,7 @@ describe('ReportUtils', () => { login: '+@domain.com', excludeEmptyChats: false, includeDomainEmail: false, + draftComment: '', }), ).toBeFalsy(); }); @@ -4017,6 +4032,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeFalsy(); }); @@ -4035,6 +4051,7 @@ describe('ReportUtils', () => { betas, doesReportHaveViolations: false, excludeEmptyChats: false, + draftComment: '', }), ).toBeFalsy(); }); @@ -4067,6 +4084,7 @@ describe('ReportUtils', () => { betas: [], doesReportHaveViolations: false, excludeEmptyChats: true, + draftComment: '', }), ).toBeFalsy(); }); diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts index 6ef626263f045..306fb6aa65b20 100644 --- a/tests/unit/SidebarUtilsTest.ts +++ b/tests/unit/SidebarUtilsTest.ts @@ -27,14 +27,6 @@ import {localeCompare} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; -// Mock DraftCommentUtils -jest.mock('@libs/DraftCommentUtils', () => ({ - hasValidDraftComment: jest.fn(), - getDraftComment: jest.fn(), - isValidDraftComment: jest.fn(), - prepareDraftComment: jest.fn(), -})); - // Mock PolicyUtils jest.mock('@libs/PolicyUtils', () => ({ ...jest.requireActual('@libs/PolicyUtils'), @@ -1645,14 +1637,15 @@ describe('SidebarUtils', () => { describe('sortReportsToDisplayInLHN', () => { describe('categorizeReportsForLHN', () => { it('should categorize reports into correct groups', () => { - // Given hasValidDraftComment is mocked to return true for report '2' - const {hasValidDraftComment} = require('@libs/DraftCommentUtils') as {hasValidDraftComment: jest.Mock}; - hasValidDraftComment.mockImplementation((reportID: string) => reportID === '2'); - const {reports, reportNameValuePairs, reportAttributes} = createSidebarTestData(); + // Given reportsDrafts contains a draft comment for report '2' + const reportsDrafts = { + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}2`]: 'test', + }; + // When the reports are categorized - const result = SidebarUtils.categorizeReportsForLHN(reports, reportNameValuePairs, reportAttributes); + const result = SidebarUtils.categorizeReportsForLHN(reports, reportsDrafts, reportNameValuePairs, reportAttributes); // Then the reports are categorized into the correct groups expect(result.pinnedAndGBRReports).toHaveLength(1); @@ -1688,7 +1681,7 @@ describe('SidebarUtils', () => { }; // When the reports are categorized - const result = SidebarUtils.categorizeReportsForLHN(reports, undefined, reportAttributes); + const result = SidebarUtils.categorizeReportsForLHN(reports, undefined, undefined, reportAttributes); // Then the reports are categorized into the correct groups expect(result.pinnedAndGBRReports).toHaveLength(1); @@ -1715,7 +1708,7 @@ describe('SidebarUtils', () => { }; // When the reports are categorized - const result = SidebarUtils.categorizeReportsForLHN(reports); + const result = SidebarUtils.categorizeReportsForLHN(reports, {}); // Then the reports are categorized into the correct groups expect(result.pinnedAndGBRReports).toHaveLength(0); @@ -1729,7 +1722,7 @@ describe('SidebarUtils', () => { it('should handle empty reports object', () => { // Given the reports are empty - const result = SidebarUtils.categorizeReportsForLHN({}); + const result = SidebarUtils.categorizeReportsForLHN({}, {}); // Then the reports are categorized into the correct groups expect(result.pinnedAndGBRReports).toHaveLength(0); @@ -1966,7 +1959,7 @@ describe('SidebarUtils', () => { const priorityMode = CONST.PRIORITY_MODE.DEFAULT; // When the reports are sorted - const result = SidebarUtils.sortReportsToDisplayInLHN(reports, priorityMode, mockLocaleCompare); + const result = SidebarUtils.sortReportsToDisplayInLHN(reports, priorityMode, mockLocaleCompare, undefined); // Then the reports are sorted in the correct order expect(result).toEqual(['0', '1', '2']); // Pinned first, Error second, Normal third @@ -1992,10 +1985,10 @@ describe('SidebarUtils', () => { const mockLocaleCompare = (a: string, b: string) => a.localeCompare(b); // When the reports are sorted in default mode - const defaultResult = SidebarUtils.sortReportsToDisplayInLHN(reports, CONST.PRIORITY_MODE.DEFAULT, mockLocaleCompare); + const defaultResult = SidebarUtils.sortReportsToDisplayInLHN(reports, CONST.PRIORITY_MODE.DEFAULT, mockLocaleCompare, undefined); // When the reports are sorted in GSD mode - const gsdResult = SidebarUtils.sortReportsToDisplayInLHN(reports, CONST.PRIORITY_MODE.GSD, mockLocaleCompare); + const gsdResult = SidebarUtils.sortReportsToDisplayInLHN(reports, CONST.PRIORITY_MODE.GSD, mockLocaleCompare, undefined); // Then the reports are sorted in the correct order expect(defaultResult).toEqual(['1', '0']); // Most recent first (index 1 has later date) diff --git a/tests/unit/UnreadIndicatorUpdaterTest.ts b/tests/unit/UnreadIndicatorUpdaterTest.ts index cf8119f3784af..ad238822b8320 100644 --- a/tests/unit/UnreadIndicatorUpdaterTest.ts +++ b/tests/unit/UnreadIndicatorUpdaterTest.ts @@ -29,7 +29,7 @@ describe('UnreadIndicatorUpdaterTest', () => { 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastMessageText: 'test'}, }; TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID).then(() => { - expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(2); + expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3', undefined).length).toBe(2); }); }); @@ -39,7 +39,7 @@ describe('UnreadIndicatorUpdaterTest', () => { 2: {reportID: '2', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, 3: {reportID: '3', type: CONST.REPORT.TYPE.TASK}, }; - expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(0); + expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3', undefined).length).toBe(0); }); it('given notification preference of some reports is hidden', () => { @@ -68,7 +68,7 @@ describe('UnreadIndicatorUpdaterTest', () => { 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastMessageText: 'test'}, }; TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID).then(() => { - expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(1); + expect(UnreadIndicatorUpdater.getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3', undefined).length).toBe(1); }); }); }); diff --git a/tests/unit/useSidebarOrderedReportsTest.tsx b/tests/unit/useSidebarOrderedReportsTest.tsx index 8e9baaf7db2bb..aaa407e5ee5d8 100644 --- a/tests/unit/useSidebarOrderedReportsTest.tsx +++ b/tests/unit/useSidebarOrderedReportsTest.tsx @@ -61,6 +61,7 @@ describe('useSidebarOrderedReports', () => { [ONYXKEYS.COLLECTION.TRANSACTION]: {}, [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: {}, [ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS]: {}, + [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: {}, [ONYXKEYS.BETAS]: [], [ONYXKEYS.DERIVED.REPORT_ATTRIBUTES]: {reports: {}}, } as unknown as OnyxMultiSetInput); @@ -70,7 +71,7 @@ describe('useSidebarOrderedReports', () => { // Default mock implementations mockSidebarUtils.getReportsToDisplayInLHN.mockImplementation(() => ({})); - mockSidebarUtils.updateReportsToDisplayInLHN.mockImplementation((prev) => prev); + mockSidebarUtils.updateReportsToDisplayInLHN.mockImplementation(({displayedReports}) => ({...displayedReports})); mockSidebarUtils.sortReportsToDisplayInLHN.mockReturnValue([]); await waitForBatchedUpdatesWithAct(); @@ -120,7 +121,7 @@ describe('useSidebarOrderedReports', () => { // When the initial reports are set const initialReports = createMockReports(reportsContent); mockSidebarUtils.getReportsToDisplayInLHN.mockReturnValue(initialReports); - mockSidebarUtils.updateReportsToDisplayInLHN.mockImplementation((prev) => ({...prev})); + mockSidebarUtils.updateReportsToDisplayInLHN.mockImplementation(({displayedReports}) => ({...displayedReports})); currentReportIDForTestsValue = '1'; // When the hook is rendered @@ -199,6 +200,7 @@ describe('useSidebarOrderedReports', () => { updatedReports, expect.any(String), // priorityMode expect.any(Function), // localeCompare + expect.any(Object), // reportsDrafts expect.any(Object), // reportNameValuePairs expect.any(Object), // reportAttributes );