diff --git a/src/hooks/useSearchSections.ts b/src/hooks/useSearchSections.ts index b889b0519086..8ff6ac3f08b6 100644 --- a/src/hooks/useSearchSections.ts +++ b/src/hooks/useSearchSections.ts @@ -55,6 +55,7 @@ function useSearchSections(): UseSearchSectionsResult { formatPhoneNumber, bankAccountList, groupBy, + queryJSON: lastSearchQuery?.queryJSON, currentSearch: searchKey, reportNameValuePairs, isActionLoadingSet, diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index fb57d415d458..3bc12da9eee1 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2996,7 +2996,7 @@ function getReportSections({ reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportItem.reportID}`] ?? Object.values(data[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportItem.reportID}`] ?? {}); const isActionLoading = !!isActionLoadingSet?.has(`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${reportItem.reportID}`); - const shouldShow = !isActionLoading && currentQueryJSON?.type === CONST.SEARCH.DATA_TYPES.EXPENSE ? isEligibleForStatus(currentQueryJSON, reportItem) : true; + const shouldShow = isActionLoading || isEligibleForStatus(currentQueryJSON, reportItem); if (shouldShow) { const reportPendingAction = diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index e6ec008cdb34..0fbcad0a345e 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -5982,6 +5982,21 @@ describe('SearchUIUtils', () => { }; } + // `getSections` only routes to `getReportSections` for `expense-report`, so an `expense` query never + // exercises that path in production. + function makeExpenseReportQueryJSON(status: string[] | undefined, isNegated = false) { + return { + ...makeExpenseQueryJSON(status, isNegated), + type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, + inputQuery: 'type:expense-report' as const, + filters: { + operator: CONST.SEARCH.SYNTAX_OPERATORS.AND, + left: CONST.SEARCH.SYNTAX_FILTER_KEYS.TYPE, + right: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, + }, + }; + } + describe('getTransactionsSections filtering and edge cases', () => { const filterTestReportID = 'filter-report-1'; const filterTestTxID = 'filter-tx-1'; @@ -6427,6 +6442,46 @@ describe('SearchUIUtils', () => { expect(sections.some((s) => s.keyForList === rptFilterReportID)).toBe(true); }); + it('should exclude a submitted report from the drafts filter on an expense-report query', () => { + const data = makeReportFilterTestData({stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, type: CONST.REPORT.TYPE.EXPENSE}); + const [sections] = callGetReportSections(data, {queryJSON: makeExpenseReportQueryJSON([CONST.SEARCH.STATUS.EXPENSE.DRAFTS])}); + expect(sections.some((s) => s.keyForList === rptFilterReportID)).toBe(false); + }); + + it('should keep a draft report under the drafts filter on an expense-report query', () => { + const data = makeReportFilterTestData({stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS_NUM.OPEN, type: CONST.REPORT.TYPE.EXPENSE}); + const [sections] = callGetReportSections(data, {queryJSON: makeExpenseReportQueryJSON([CONST.SEARCH.STATUS.EXPENSE.DRAFTS])}); + expect(sections.some((s) => s.keyForList === rptFilterReportID)).toBe(true); + }); + + it('should exclude a paid report from the approved filter on an expense-report query', () => { + const data = makeReportFilterTestData({stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, type: CONST.REPORT.TYPE.EXPENSE}); + const [sections] = callGetReportSections(data, {queryJSON: makeExpenseReportQueryJSON([CONST.SEARCH.STATUS.EXPENSE.APPROVED])}); + expect(sections.some((s) => s.keyForList === rptFilterReportID)).toBe(false); + }); + + it('should show every report when an expense-report query has no status filter', () => { + const data = makeReportFilterTestData({stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, type: CONST.REPORT.TYPE.EXPENSE}); + const [sections] = callGetReportSections(data, {queryJSON: makeExpenseReportQueryJSON(undefined)}); + expect(sections.some((s) => s.keyForList === rptFilterReportID)).toBe(true); + }); + + it('should honor a negated status filter on an expense-report query', () => { + const data = makeReportFilterTestData({stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, type: CONST.REPORT.TYPE.EXPENSE}); + const [sections] = callGetReportSections(data, {queryJSON: makeExpenseReportQueryJSON([CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING], true)}); + expect(sections.some((s) => s.keyForList === rptFilterReportID)).toBe(false); + }); + + it('should keep a report visible while its action is loading on an expense-report query', () => { + const data = makeReportFilterTestData({stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, type: CONST.REPORT.TYPE.EXPENSE}); + const loadingSet = new Set([`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${rptFilterReportID}`]); + const [sections] = callGetReportSections(data, { + queryJSON: makeExpenseReportQueryJSON([CONST.SEARCH.STATUS.EXPENSE.DRAFTS]), + isActionLoadingSet: loadingSet, + }); + expect(sections.some((s) => s.keyForList === rptFilterReportID)).toBe(true); + }); + it('should drop transactions when their parent report is filtered out', () => { const data = makeReportFilterTestData({stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS_NUM.OPEN, type: CONST.REPORT.TYPE.EXPENSE}); const [sections] = callGetReportSections(data, {queryJSON: makeExpenseQueryJSON([CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING])});